Snap for 8505378 from 8e5ba105c7d1e4606f79d5ca941be1dfe57261a6 to mainline-go-wifi-release

Change-Id: Ie1605cd6f5261b1e9572fcb502d70175a5646937
diff --git a/cmds/atrace/atrace.rc b/cmds/atrace/atrace.rc
index 1c3a4f2..32ffe14 100644
--- a/cmds/atrace/atrace.rc
+++ b/cmds/atrace/atrace.rc
@@ -63,6 +63,8 @@
     chmod 0666 /sys/kernel/tracing/events/cpuhp/cpuhp_pause/enable
     chmod 0666 /sys/kernel/debug/tracing/events/power/gpu_frequency/enable
     chmod 0666 /sys/kernel/tracing/events/power/gpu_frequency/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/power/gpu_work_period/enable
+    chmod 0666 /sys/kernel/tracing/events/power/gpu_work_period/enable
     chmod 0666 /sys/kernel/debug/tracing/events/power/suspend_resume/enable
     chmod 0666 /sys/kernel/tracing/events/power/suspend_resume/enable
     chmod 0666 /sys/kernel/debug/tracing/events/cpufreq_interactive/enable
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 24b201f..890c15f 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -2733,8 +2733,8 @@
                                         const android::base::unique_fd& screenshot_fd_in,
                                         bool is_screenshot_requested) {
     // Duplicate the fds because the passed in fds don't outlive the binder transaction.
-    bugreport_fd.reset(dup(bugreport_fd_in.get()));
-    screenshot_fd.reset(dup(screenshot_fd_in.get()));
+    bugreport_fd.reset(fcntl(bugreport_fd_in.get(), F_DUPFD_CLOEXEC, 0));
+    screenshot_fd.reset(fcntl(screenshot_fd_in.get(), F_DUPFD_CLOEXEC, 0));
 
     SetOptionsFromMode(bugreport_mode, this, is_screenshot_requested);
 }
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp
index c104fea..b4aa88e 100644
--- a/cmds/installd/InstalldNativeService.cpp
+++ b/cmds/installd/InstalldNativeService.cpp
@@ -473,7 +473,8 @@
                 StringPrintf("%smisc/installd/using_project_ids", android_data_dir.c_str());
         sUsingProjectIdsFlag = access(using_project_ids.c_str(), F_OK) == 0;
     });
-    return sUsingProjectIdsFlag;
+    // return sUsingProjectIdsFlag;
+    return false;
 }
 
 static int prepare_app_dir(const std::string& path, mode_t target_mode, uid_t uid, gid_t gid,
@@ -632,8 +633,9 @@
 }
 
 static binder::Status createAppDataDirs(const std::string& path, int32_t uid, int32_t gid,
-                                        int32_t* previousUid, int32_t cacheGid,
-                                        const std::string& seInfo, mode_t targetMode) {
+                                        int32_t previousUid, int32_t cacheGid,
+                                        const std::string& seInfo, mode_t targetMode,
+                                        long projectIdApp, long projectIdCache) {
     struct stat st{};
     bool parent_dir_exists = (stat(path.c_str(), &st) == 0);
 
@@ -643,25 +645,17 @@
     bool code_cache_exists = (access(code_cache_path.c_str(), F_OK) == 0);
 
     if (parent_dir_exists) {
-        if (*previousUid < 0) {
-            // If previousAppId is -1 in CreateAppDataArgs, we will assume the current owner
-            // of the directory as previousUid. This is required because it is not always possible
-            // to chown app data during app upgrade (e.g. secondary users' CE storage not unlocked)
-            *previousUid = st.st_uid;
-        }
-        if (*previousUid != uid) {
-            if (!chown_app_dir(path, uid, *previousUid, cacheGid)) {
+        if (previousUid > 0 && previousUid != uid) {
+            if (!chown_app_dir(path, uid, previousUid, cacheGid)) {
                 return error("Failed to chown " + path);
             }
         }
     }
 
     // Prepare only the parent app directory
-    long project_id_app = get_project_id(uid, PROJECT_ID_APP_START);
-    long project_id_cache_app = get_project_id(uid, PROJECT_ID_APP_CACHE_START);
-    if (prepare_app_dir(path, targetMode, uid, gid, project_id_app) ||
-        prepare_app_cache_dir(path, "cache", 02771, uid, cacheGid, project_id_cache_app) ||
-        prepare_app_cache_dir(path, "code_cache", 02771, uid, cacheGid, project_id_cache_app)) {
+    if (prepare_app_dir(path, targetMode, uid, gid, projectIdApp) ||
+        prepare_app_cache_dir(path, "cache", 02771, uid, cacheGid, projectIdCache) ||
+        prepare_app_cache_dir(path, "code_cache", 02771, uid, cacheGid, projectIdCache)) {
         return error("Failed to prepare " + path);
     }
 
@@ -702,12 +696,9 @@
 
     int32_t uid = multiuser_get_uid(userId, appId);
 
-    // If previousAppId < 0, we will use the existing app data owner as previousAppUid
-    // If previousAppId == 0, we use uid as previousUid (no data migration will happen)
-    // if previousAppId > 0, an app is upgrading and changing its app ID
-    int32_t previousUid = previousAppId > 0
-        ? (int32_t) multiuser_get_uid(userId, previousAppId)
-        : (previousAppId == 0 ? uid : -1);
+    // If previousAppId > 0, an app is changing its app ID
+    int32_t previousUid =
+            previousAppId > 0 ? (int32_t)multiuser_get_uid(userId, previousAppId) : -1;
 
     int32_t cacheGid = multiuser_get_cache_gid(userId, appId);
     mode_t targetMode = targetSdkVersion >= MIN_RESTRICTED_HOME_SDK_VERSION ? 0700 : 0751;
@@ -717,10 +708,14 @@
         cacheGid = uid;
     }
 
+    long projectIdApp = get_project_id(uid, PROJECT_ID_APP_START);
+    long projectIdCache = get_project_id(uid, PROJECT_ID_APP_CACHE_START);
+
     if (flags & FLAG_STORAGE_CE) {
         auto path = create_data_user_ce_package_path(uuid_, userId, pkgname);
 
-        auto status = createAppDataDirs(path, uid, uid, &previousUid, cacheGid, seInfo, targetMode);
+        auto status = createAppDataDirs(path, uid, uid, previousUid, cacheGid, seInfo, targetMode,
+                                        projectIdApp, projectIdCache);
         if (!status.isOk()) {
             return status;
         }
@@ -745,11 +740,12 @@
     if (flags & FLAG_STORAGE_DE) {
         auto path = create_data_user_de_package_path(uuid_, userId, pkgname);
 
-        auto status = createAppDataDirs(path, uid, uid, &previousUid, cacheGid, seInfo, targetMode);
+        auto status = createAppDataDirs(path, uid, uid, previousUid, cacheGid, seInfo, targetMode,
+                                        projectIdApp, projectIdCache);
         if (!status.isOk()) {
             return status;
         }
-        if (previousUid != uid) {
+        if (previousUid > 0 && previousUid != uid) {
             chown_app_profile_dir(packageName, appId, userId);
         }
 
@@ -945,8 +941,12 @@
             }
             const int32_t sandboxUid = multiuser_get_sdk_sandbox_uid(userId, appId);
             int32_t previousSandboxUid = multiuser_get_sdk_sandbox_uid(userId, previousAppId);
-            auto status = createAppDataDirs(path, sandboxUid, AID_NOBODY, &previousSandboxUid,
-                                            cacheGid, seInfo, 0700 | S_ISGID);
+            int32_t appUid = multiuser_get_uid(userId, appId);
+            long projectIdApp = get_project_id(appUid, PROJECT_ID_APP_START);
+            long projectIdCache = get_project_id(appUid, PROJECT_ID_APP_CACHE_START);
+            auto status =
+                    createAppDataDirs(path, sandboxUid, AID_NOBODY, previousSandboxUid, cacheGid,
+                                      seInfo, 0700 | S_ISGID, projectIdApp, projectIdCache);
             if (!status.isOk()) {
                 res = status;
                 continue;
@@ -1176,6 +1176,25 @@
     return res;
 }
 
+binder::Status InstalldNativeService::deleteReferenceProfile(const std::string& packageName,
+                                                             const std::string& profileName) {
+    ENFORCE_UID(AID_SYSTEM);
+    CHECK_ARGUMENT_PACKAGE_NAME(packageName);
+    LOCK_PACKAGE();
+
+    // This function only supports primary dex'es.
+    std::string path =
+            create_reference_profile_path(packageName, profileName, /*is_secondary_dex=*/false);
+    if (unlink(path.c_str()) != 0) {
+        if (errno == ENOENT) {
+            return ok();
+        } else {
+            return error("Failed to delete profile " + profileName + " for " + packageName);
+        }
+    }
+    return ok();
+}
+
 binder::Status InstalldNativeService::destroyAppData(const std::optional<std::string>& uuid,
         const std::string& packageName, int32_t userId, int32_t flags, int64_t ceDataInode) {
     ENFORCE_UID(AID_SYSTEM);
@@ -2957,13 +2976,15 @@
 // Dumps the contents of a profile file, using pkgname's dex files for pretty
 // printing the result.
 binder::Status InstalldNativeService::dumpProfiles(int32_t uid, const std::string& packageName,
-        const std::string& profileName, const std::string& codePath, bool* _aidl_return) {
+                                                   const std::string& profileName,
+                                                   const std::string& codePath,
+                                                   bool dumpClassesAndMethods, bool* _aidl_return) {
     ENFORCE_UID(AID_SYSTEM);
     CHECK_ARGUMENT_PACKAGE_NAME(packageName);
     CHECK_ARGUMENT_PATH(codePath);
     LOCK_PACKAGE();
 
-    *_aidl_return = dump_profiles(uid, packageName, profileName, codePath);
+    *_aidl_return = dump_profiles(uid, packageName, profileName, codePath, dumpClassesAndMethods);
     return ok();
 }
 
@@ -3410,7 +3431,7 @@
     auto temp_path = StringPrintf("%smisc/installd/ioctl_check", android_data_dir.c_str());
     if (access(temp_path.c_str(), F_OK) != 0) {
         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;
+        result = set_quota_project_id(temp_path, 0, false) == 0;
         close(fd);
         // delete the temp file
         remove(temp_path.c_str());
diff --git a/cmds/installd/InstalldNativeService.h b/cmds/installd/InstalldNativeService.h
index 87a9206..521afc3 100644
--- a/cmds/installd/InstalldNativeService.h
+++ b/cmds/installd/InstalldNativeService.h
@@ -134,12 +134,15 @@
     binder::Status mergeProfiles(int32_t uid, const std::string& packageName,
             const std::string& profileName, int* _aidl_return);
     binder::Status dumpProfiles(int32_t uid, const std::string& packageName,
-            const std::string& profileName, const std::string& codePath, bool* _aidl_return);
+                                const std::string& profileName, const std::string& codePath,
+                                bool dumpClassesAndMethods, bool* _aidl_return);
     binder::Status copySystemProfile(const std::string& systemProfile,
             int32_t uid, const std::string& packageName, const std::string& profileName,
             bool* _aidl_return);
     binder::Status clearAppProfiles(const std::string& packageName, const std::string& profileName);
     binder::Status destroyAppProfiles(const std::string& packageName);
+    binder::Status deleteReferenceProfile(const std::string& packageName,
+                                          const std::string& profileName);
 
     binder::Status createProfileSnapshot(int32_t appId, const std::string& packageName,
             const std::string& profileName, const std::string& classpath, bool* _aidl_return);
diff --git a/cmds/installd/TEST_MAPPING b/cmds/installd/TEST_MAPPING
index 8ccab4c..fc4cfc9 100644
--- a/cmds/installd/TEST_MAPPING
+++ b/cmds/installd/TEST_MAPPING
@@ -32,7 +32,12 @@
       "name": "CtsCompilationTestCases"
     },
     {
-      "name": "SdkSandboxStorageHostTest"
+      "name": "SdkSandboxStorageHostTest",
+      "options": [
+        {
+          "exclude-annotation": "android.platform.test.annotations.LargeTest"
+        }
+      ]
     }
   ]
 }
diff --git a/cmds/installd/binder/android/os/IInstalld.aidl b/cmds/installd/binder/android/os/IInstalld.aidl
index 79c02e8..9ad853b 100644
--- a/cmds/installd/binder/android/os/IInstalld.aidl
+++ b/cmds/installd/binder/android/os/IInstalld.aidl
@@ -77,11 +77,12 @@
 
     int mergeProfiles(int uid, @utf8InCpp String packageName, @utf8InCpp String profileName);
     boolean dumpProfiles(int uid, @utf8InCpp String packageName, @utf8InCpp String  profileName,
-            @utf8InCpp String codePath);
+            @utf8InCpp String codePath, boolean dumpClassesAndMethods);
     boolean copySystemProfile(@utf8InCpp String systemProfile, int uid,
             @utf8InCpp String packageName, @utf8InCpp String profileName);
     void clearAppProfiles(@utf8InCpp String packageName, @utf8InCpp String profileName);
     void destroyAppProfiles(@utf8InCpp String packageName);
+    void deleteReferenceProfile(@utf8InCpp String packageName, @utf8InCpp String profileName);
 
     boolean createProfileSnapshot(int appId, @utf8InCpp String packageName,
             @utf8InCpp String profileName, @utf8InCpp String classpath);
diff --git a/cmds/installd/dexopt.cpp b/cmds/installd/dexopt.cpp
index 894c7d3..ebb7891 100644
--- a/cmds/installd/dexopt.cpp
+++ b/cmds/installd/dexopt.cpp
@@ -624,12 +624,15 @@
                   /*for_boot_image*/false);
     }
 
-    void SetupDump(const std::vector<unique_fd>& profiles_fd,
-                   const unique_fd& reference_profile_fd,
+    void SetupDump(const std::vector<unique_fd>& profiles_fd, const unique_fd& reference_profile_fd,
                    const std::vector<std::string>& dex_locations,
-                   const std::vector<unique_fd>& apk_fds,
+                   const std::vector<unique_fd>& apk_fds, bool dump_classes_and_methods,
                    const unique_fd& output_fd) {
-        AddArg("--dump-only");
+        if (dump_classes_and_methods) {
+            AddArg("--dump-classes-and-methods");
+        } else {
+            AddArg("--dump-only");
+        }
         AddArg(StringPrintf("--dump-output-to-fd=%d", output_fd.get()));
         SetupArgs(profiles_fd,
                   reference_profile_fd,
@@ -772,7 +775,7 @@
 }
 
 bool dump_profiles(int32_t uid, const std::string& pkgname, const std::string& profile_name,
-        const std::string& code_path) {
+                   const std::string& code_path, bool dump_classes_and_methods) {
     std::vector<unique_fd> profile_fds;
     unique_fd reference_profile_fd;
     std::string out_file_name = StringPrintf("/data/misc/profman/%s-%s.txt",
@@ -808,7 +811,8 @@
 
 
     RunProfman profman_dump;
-    profman_dump.SetupDump(profile_fds, reference_profile_fd, dex_locations, apk_fds, output_fd);
+    profman_dump.SetupDump(profile_fds, reference_profile_fd, dex_locations, apk_fds,
+                           dump_classes_and_methods, output_fd);
     pid_t pid = fork();
     if (pid == 0) {
         /* child -- drop privileges before continuing */
diff --git a/cmds/installd/dexopt.h b/cmds/installd/dexopt.h
index f7af929..5cf402c 100644
--- a/cmds/installd/dexopt.h
+++ b/cmds/installd/dexopt.h
@@ -88,10 +88,8 @@
                              const std::string& profile_name,
                              const std::string& classpath);
 
-bool dump_profiles(int32_t uid,
-                   const std::string& pkgname,
-                   const std::string& profile_name,
-                   const std::string& code_path);
+bool dump_profiles(int32_t uid, const std::string& pkgname, const std::string& profile_name,
+                   const std::string& code_path, bool dump_classes_and_methods);
 
 bool copy_system_profile(const std::string& system_profile,
                          uid_t packageUid,
diff --git a/cmds/installd/tests/installd_service_test.cpp b/cmds/installd/tests/installd_service_test.cpp
index 38cb370..f86f1d5 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, GetAppSize) {
+TEST_F(ServiceTest, GetAppSizeManualForMedia) {
     struct stat s;
 
     std::string externalPicDir =
@@ -509,6 +509,7 @@
         system(removeCommand.c_str());
     }
 }
+
 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 9801a9b..45aeab6 100644
--- a/cmds/installd/utils.cpp
+++ b/cmds/installd/utils.cpp
@@ -707,16 +707,16 @@
     auto temp_dir_path =
             base::StringPrintf("%s/%s", Dirname(pathname).c_str(), temp_dir_name.c_str());
 
-    if (::rename(pathname.c_str(), temp_dir_path.c_str())) {
+    auto dir_to_delete = temp_dir_path.c_str();
+    if (::rename(pathname.c_str(), dir_to_delete)) {
         if (ignore_if_missing && (errno == ENOENT)) {
             return 0;
         }
-        ALOGE("Couldn't rename %s -> %s: %s \n", pathname.c_str(), temp_dir_path.c_str(),
-              strerror(errno));
-        return -errno;
+        ALOGE("Couldn't rename %s -> %s: %s \n", pathname.c_str(), dir_to_delete, strerror(errno));
+        dir_to_delete = pathname.c_str();
     }
 
-    return delete_dir_contents(temp_dir_path.c_str(), 1, exclusion_predicate, ignore_if_missing);
+    return delete_dir_contents(dir_to_delete, 1, exclusion_predicate, ignore_if_missing);
 }
 
 bool is_renamed_deleted_dir(const std::string& path) {
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 5003151..bdd5172 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -227,6 +227,12 @@
 }
 
 prebuilt_etc {
+    name: "android.software.opengles.deqp.level-2022-03-01.prebuilt.xml",
+    src: "android.software.opengles.deqp.level-2022-03-01.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "android.software.sip.voip.prebuilt.xml",
     src: "android.software.sip.voip.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
@@ -245,6 +251,12 @@
 }
 
 prebuilt_etc {
+    name: "android.software.vulkan.deqp.level-2022-03-01.prebuilt.xml",
+    src: "android.software.vulkan.deqp.level-2022-03-01.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "aosp_excluded_hardware.prebuilt.xml",
     src: "aosp_excluded_hardware.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
diff --git a/include/android/multinetwork.h b/include/android/multinetwork.h
index 4c83a14..ee392fc 100644
--- a/include/android/multinetwork.h
+++ b/include/android/multinetwork.h
@@ -235,7 +235,7 @@
  *
  * Available since API level 33.
  */
-int android_tag_socket_with_uid(int sockfd, int tag, uid_t uid) __INTRODUCED_IN(33);
+int android_tag_socket_with_uid(int sockfd, uint32_t tag, uid_t uid) __INTRODUCED_IN(33);
 
 /*
  * Set the socket tag for traffic statistics on the specified socket.
@@ -245,14 +245,26 @@
  * opened by another UID or was previously tagged by another UID. Subsequent
  * calls always replace any existing parameters. The socket tag is kept when the
  * socket is sent to another process using binder IPCs or other mechanisms such
- * as UNIX socket fd passing.
+ * as UNIX socket fd passing. The tag is a value defined by the caller and used
+ * together with uid for data traffic accounting, so that the function callers
+ * can account different types of data usage for a uid.
  *
  * Returns 0 on success, or a negative POSIX error code (see errno.h) on
  * failure.
  *
+ * Some possible error codes:
+ * -EBADF           Bad socketfd.
+ * -EPERM           No permission.
+ * -EAFNOSUPPORT    Socket family is neither AF_INET nor AF_INET6.
+ * -EPROTONOSUPPORT Socket protocol is neither IPPROTO_UDP nor IPPROTO_TCP.
+ * -EMFILE          Too many stats entries.
+ * There are still other error codes that may provided by -errno of
+ * [getsockopt()](https://man7.org/linux/man-pages/man2/getsockopt.2.html) or by
+ * BPF maps read/write sys calls, which are set appropriately.
+ *
  * Available since API level 33.
  */
-int android_tag_socket(int sockfd, int tag) __INTRODUCED_IN(33);
+int android_tag_socket(int sockfd, uint32_t tag) __INTRODUCED_IN(33);
 
 /*
  * Untag a network socket.
@@ -267,6 +279,12 @@
  * Returns 0 on success, or a negative POSIX error code (see errno.h) on
  * failure.
  *
+ * One of possible error code:
+ * -EBADF           Bad socketfd.
+ * Other error codes are either provided by -errno of
+ * [getsockopt()](https://man7.org/linux/man-pages/man2/getsockopt.2.html) or by
+ * BPF map element deletion sys call, which are set appropriately.
+ *
  * Available since API level 33.
  */
 int android_untag_socket(int sockfd) __INTRODUCED_IN(33);
diff --git a/include/android/sensor.h b/include/android/sensor.h
index c714b05..6112d5f 100644
--- a/include/android/sensor.h
+++ b/include/android/sensor.h
@@ -213,6 +213,13 @@
      */
     ASENSOR_TYPE_HEART_BEAT = 31,
     /**
+     * A constant describing a dynamic sensor meta event sensor.
+     *
+     * A sensor event of this type is received when a dynamic sensor is added to or removed from
+     * the system. This sensor type should always use special trigger report mode.
+     */
+    ASENSOR_TYPE_DYNAMIC_SENSOR_META = 32,
+    /**
      * This sensor type is for delivering additional sensor information aside
      * from sensor event data.
      *
@@ -761,6 +768,10 @@
  * Each time this is called, the previously returned list is deallocated and
  * must no longer be used.
  *
+ * Clients should call this if they receive a sensor update from
+ * {@link ASENSOR_TYPE_DYNAMIC_SENSOR_META} indicating the sensors have changed.
+ * If this happens, previously received lists from this method will be stale.
+ *
  * Available since API level 33.
  *
  * \param manager the {@link ASensorManager} instance obtained from
diff --git a/include/ftl/enum.h b/include/ftl/enum.h
index 5234c05..82af1d6 100644
--- a/include/ftl/enum.h
+++ b/include/ftl/enum.h
@@ -261,10 +261,10 @@
   const auto value = to_underlying(v);
 
   // TODO: Replace with std::popcount and std::countr_zero in C++20.
-  if (__builtin_popcountl(value) != 1) return {};
+  if (__builtin_popcountll(value) != 1) return {};
 
   constexpr auto kRange = details::EnumRange<E, details::FlagName>{};
-  return kRange.values[__builtin_ctzl(value)];
+  return kRange.values[__builtin_ctzll(value)];
 }
 
 // Returns a stringified enumerator, or its integral value if not named.
diff --git a/include/ftl/fake_guard.h b/include/ftl/fake_guard.h
new file mode 100644
index 0000000..bacd1b2
--- /dev/null
+++ b/include/ftl/fake_guard.h
@@ -0,0 +1,90 @@
+/*
+ * 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
+
+#define FTL_ATTRIBUTE(a) __attribute__((a))
+
+namespace android::ftl {
+
+// Granular alternative to [[clang::no_thread_safety_analysis]]. Given a std::mutex-like object,
+// FakeGuard suppresses enforcement of thread-safe access to guarded variables within its scope.
+// While FakeGuard is scoped to a block, there are macro shorthands for a single expression, as
+// well as function/lambda scope (though calls must be indirect, e.g. virtual or std::function):
+//
+//   struct {
+//     std::mutex mutex;
+//     int x FTL_ATTRIBUTE(guarded_by(mutex)) = -1;
+//
+//     int f() {
+//       {
+//         ftl::FakeGuard guard(mutex);
+//         x = 0;
+//       }
+//
+//       return FTL_FAKE_GUARD(mutex, x + 1);
+//     }
+//
+//      std::function<int()> g() const {
+//        return [this]() FTL_FAKE_GUARD(mutex) { return x; };
+//      }
+//   } s;
+//
+//   assert(s.f() == 1);
+//   assert(s.g()() == 0);
+//
+// An example of a situation where FakeGuard helps is a mutex that guards writes on Thread 1, and
+// reads on Thread 2. Reads on Thread 1, which is the only writer, need not be under lock, so can
+// use FakeGuard to appease the thread safety analyzer. Another example is enforcing and documenting
+// exclusive access by a single thread. This is done by defining a global constant that represents a
+// thread context, and annotating guarded variables as if it were a mutex (though without any effect
+// at run time):
+//
+//   constexpr class [[clang::capability("mutex")]] {
+//   } kMainThreadContext;
+//
+template <typename Mutex>
+struct [[clang::scoped_lockable]] FakeGuard final {
+  explicit FakeGuard(const Mutex& mutex) FTL_ATTRIBUTE(acquire_capability(mutex)) {}
+  [[clang::release_capability()]] ~FakeGuard() {}
+
+  FakeGuard(const FakeGuard&) = delete;
+  FakeGuard& operator=(const FakeGuard&) = delete;
+};
+
+}  // namespace android::ftl
+
+// TODO: Enable in C++23 once standard attributes can be used on lambdas.
+#if 0
+#define FTL_FAKE_GUARD1(mutex) [[using clang: acquire_capability(mutex), release_capability(mutex)]]
+#else
+#define FTL_FAKE_GUARD1(mutex)             \
+  FTL_ATTRIBUTE(acquire_capability(mutex)) \
+  FTL_ATTRIBUTE(release_capability(mutex))
+#endif
+
+// The parentheses around `expr` are needed to deduce an lvalue or rvalue reference.
+#define FTL_FAKE_GUARD2(mutex, expr)            \
+  [&]() -> decltype(auto) {                     \
+    const android::ftl::FakeGuard guard(mutex); \
+    return (expr);                              \
+  }()
+
+#define FTL_MAKE_FAKE_GUARD(arg1, arg2, guard, ...) guard
+
+// The void argument suppresses a warning about zero variadic macro arguments.
+#define FTL_FAKE_GUARD(...) \
+  FTL_MAKE_FAKE_GUARD(__VA_ARGS__, FTL_FAKE_GUARD2, FTL_FAKE_GUARD1, void)(__VA_ARGS__)
diff --git a/include/ftl/Flags.h b/include/ftl/flags.h
similarity index 81%
rename from include/ftl/Flags.h
rename to include/ftl/flags.h
index 708eaf5..70aaa0e 100644
--- a/include/ftl/Flags.h
+++ b/include/ftl/flags.h
@@ -19,16 +19,15 @@
 #include <ftl/enum.h>
 #include <ftl/string.h>
 
+#include <bitset>
 #include <cstdint>
 #include <iterator>
 #include <string>
 #include <type_traits>
 
-#include "utils/BitSet.h"
+// TODO(b/185536303): Align with FTL style.
 
-// TODO(b/185536303): Align with FTL style and namespace.
-
-namespace android {
+namespace android::ftl {
 
 /* A class for handling flags defined by an enum or enum class in a type-safe way. */
 template <typename F>
@@ -49,28 +48,29 @@
     // 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, std::enable_if_t<!ftl::is_scoped_enum_v<F>, T>* = nullptr) : mFlags(t) {}
+    constexpr Flags(T t, std::enable_if_t<!is_scoped_enum_v<F>, T>* = nullptr) : mFlags(t) {}
 
     template <typename T = U>
-    explicit constexpr Flags(T t, std::enable_if_t<ftl::is_scoped_enum_v<F>, T>* = nullptr)
+    explicit constexpr Flags(T t, std::enable_if_t<is_scoped_enum_v<F>, T>* = nullptr)
           : mFlags(t) {}
 
     class Iterator {
-        // The type can't be larger than 64-bits otherwise it won't fit in BitSet64.
-        static_assert(sizeof(U) <= sizeof(uint64_t));
+        using Bits = std::uint64_t;
+        static_assert(sizeof(U) <= sizeof(Bits));
 
     public:
+        constexpr Iterator() = default;
         Iterator(Flags<F> flags) : mRemainingFlags(flags.mFlags) { (*this)++; }
-        Iterator() : mRemainingFlags(0), mCurrFlag(static_cast<F>(0)) {}
 
         // Pre-fix ++
         Iterator& operator++() {
-            if (mRemainingFlags.isEmpty()) {
-                mCurrFlag = static_cast<F>(0);
+            if (mRemainingFlags.none()) {
+                mCurrFlag = 0;
             } else {
-                uint64_t bit = mRemainingFlags.clearLastMarkedBit(); // counts from left
-                const U flag = 1 << (64 - bit - 1);
-                mCurrFlag = static_cast<F>(flag);
+                // TODO: Replace with std::countr_zero in C++20.
+                const Bits bit = static_cast<Bits>(__builtin_ctzll(mRemainingFlags.to_ullong()));
+                mRemainingFlags.reset(static_cast<std::size_t>(bit));
+                mCurrFlag = static_cast<U>(static_cast<Bits>(1) << bit);
             }
             return *this;
         }
@@ -88,7 +88,7 @@
 
         bool operator!=(Iterator other) const { return !(*this == other); }
 
-        F operator*() { return mCurrFlag; }
+        F operator*() const { return F{mCurrFlag}; }
 
         // iterator traits
 
@@ -107,8 +107,8 @@
         using pointer = void;
 
     private:
-        BitSet64 mRemainingFlags;
-        F mCurrFlag;
+        std::bitset<sizeof(Bits) * 8> mRemainingFlags;
+        U mCurrFlag = 0;
     };
 
     /*
@@ -175,7 +175,7 @@
         bool first = true;
         U unstringified = 0;
         for (const F f : *this) {
-            if (const auto flagName = ftl::flag_name(f)) {
+            if (const auto flagName = flag_name(f)) {
                 appendFlag(result, flagName.value(), first);
             } else {
                 unstringified |= static_cast<U>(f);
@@ -183,8 +183,8 @@
         }
 
         if (unstringified != 0) {
-            constexpr auto radix = sizeof(U) == 1 ? ftl::Radix::kBin : ftl::Radix::kHex;
-            appendFlag(result, ftl::to_string(unstringified, radix), first);
+            constexpr auto radix = sizeof(U) == 1 ? Radix::kBin : Radix::kHex;
+            appendFlag(result, to_string(unstringified, radix), first);
         }
 
         if (first) {
@@ -211,15 +211,15 @@
 // 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<ftl::is_scoped_enum_v<F>>>
+template <typename F, typename = std::enable_if_t<is_scoped_enum_v<F>>>
 inline Flags<F> operator~(F f) {
-    return static_cast<F>(~ftl::to_underlying(f));
+    return static_cast<F>(~to_underlying(f));
 }
 
-template <typename F, typename = std::enable_if_t<ftl::is_scoped_enum_v<F>>>
+template <typename F, typename = std::enable_if_t<is_scoped_enum_v<F>>>
 Flags<F> operator|(F lhs, F rhs) {
-    return static_cast<F>(ftl::to_underlying(lhs) | ftl::to_underlying(rhs));
+    return static_cast<F>(to_underlying(lhs) | to_underlying(rhs));
 }
 
 } // namespace flag_operators
-} // namespace android
+} // namespace android::ftl
diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h
new file mode 100644
index 0000000..7c3b29b
--- /dev/null
+++ b/include/input/PrintTools.h
@@ -0,0 +1,61 @@
+/*
+ * 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 <string>
+
+namespace android {
+
+template <typename T>
+std::string constToString(const T& v) {
+    return std::to_string(v);
+}
+
+/**
+ * Convert a set of integral types to string.
+ */
+template <typename T>
+std::string dumpSet(const std::set<T>& v, std::string (*toString)(const T&) = constToString) {
+    std::string out;
+    for (const T& entry : v) {
+        out += out.empty() ? "{" : ", ";
+        out += toString(entry);
+    }
+    return out.empty() ? "{}" : (out + "}");
+}
+
+/**
+ * Convert a map to string. Both keys and values of the map should be integral type.
+ */
+template <typename K, typename V>
+std::string dumpMap(const std::map<K, V>& map, std::string (*keyToString)(const K&) = constToString,
+                    std::string (*valueToString)(const V&) = constToString) {
+    std::string out;
+    for (const auto& [k, v] : map) {
+        if (!out.empty()) {
+            out += "\n";
+        }
+        out += keyToString(k) + ":" + valueToString(v);
+    }
+    return out;
+}
+
+const char* toString(bool value);
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/binder/Binder.cpp b/libs/binder/Binder.cpp
index 01b25d3..39befbe 100644
--- a/libs/binder/Binder.cpp
+++ b/libs/binder/Binder.cpp
@@ -582,6 +582,10 @@
 
 BBinder::~BBinder()
 {
+    if (!wasParceled() && getExtension()) {
+        ALOGW("Binder %p destroyed with extension attached before being parceled.", this);
+    }
+
     Extras* e = mExtras.load(std::memory_order_relaxed);
     if (e) delete e;
 }
diff --git a/libs/binder/IUidObserver.cpp b/libs/binder/IUidObserver.cpp
index a1b08db..d952dc7 100644
--- a/libs/binder/IUidObserver.cpp
+++ b/libs/binder/IUidObserver.cpp
@@ -57,8 +57,7 @@
     }
 
     virtual void onUidStateChanged(uid_t uid, int32_t procState, int64_t procStateSeq,
-            int32_t capability)
-    {
+                                   int32_t capability) {
         Parcel data, reply;
         data.writeInterfaceToken(IUidObserver::getInterfaceDescriptor());
         data.writeInt32((int32_t) uid);
@@ -67,6 +66,12 @@
         data.writeInt32(capability);
         remote()->transact(ON_UID_STATE_CHANGED_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY);
     }
+
+    virtual void onUidProcAdjChanged(uid_t uid) {
+        Parcel data, reply;
+        data.writeInt32((int32_t)uid);
+        remote()->transact(ON_UID_PROC_ADJ_CHANGED_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY);
+    }
 };
 
 // ----------------------------------------------------------------------
@@ -102,6 +107,7 @@
             onUidIdle(uid, disabled);
             return NO_ERROR;
         } break;
+
         case ON_UID_STATE_CHANGED_TRANSACTION: {
             CHECK_INTERFACE(IUidObserver, data, reply);
             uid_t uid = data.readInt32();
@@ -111,6 +117,14 @@
             onUidStateChanged(uid, procState, procStateSeq, capability);
             return NO_ERROR;
         } break;
+
+        case ON_UID_PROC_ADJ_CHANGED_TRANSACTION: {
+            CHECK_INTERFACE(IUidObserver, data, reply);
+            uid_t uid = data.readInt32();
+            onUidProcAdjChanged(uid);
+            return NO_ERROR;
+        } break;
+
         default:
             return BBinder::onTransact(code, data, reply, flags);
     }
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index be50a75..58b0b35 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -1584,6 +1584,7 @@
 template<class T>
 status_t Parcel::readAligned(T *pArg) const {
     static_assert(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T));
+    static_assert(std::is_trivially_copyable_v<T>);
 
     if ((mDataPos+sizeof(T)) <= mDataSize) {
         if (mObjectsSize > 0) {
@@ -1595,9 +1596,8 @@
             }
         }
 
-        const void* data = mData+mDataPos;
+        memcpy(pArg, mData + mDataPos, sizeof(T));
         mDataPos += sizeof(T);
-        *pArg =  *reinterpret_cast<const T*>(data);
         return NO_ERROR;
     } else {
         return NOT_ENOUGH_DATA;
@@ -1617,10 +1617,11 @@
 template<class T>
 status_t Parcel::writeAligned(T val) {
     static_assert(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T));
+    static_assert(std::is_trivially_copyable_v<T>);
 
     if ((mDataPos+sizeof(val)) <= mDataCapacity) {
 restart_write:
-        *reinterpret_cast<T*>(mData+mDataPos) = val;
+        memcpy(mData + mDataPos, &val, sizeof(val));
         return finishWrite(sizeof(val));
     }
 
diff --git a/libs/binder/RpcState.cpp b/libs/binder/RpcState.cpp
index 2e7084e..6d89064 100644
--- a/libs/binder/RpcState.cpp
+++ b/libs/binder/RpcState.cpp
@@ -313,7 +313,8 @@
                            const sp<RpcSession>& session, const char* what, iovec* iovs, int niovs,
                            const std::function<status_t()>& altPoll) {
     for (int i = 0; i < niovs; i++) {
-        LOG_RPC_DETAIL("Sending %s on RpcTransport %p: %s", what, connection->rpcTransport.get(),
+        LOG_RPC_DETAIL("Sending %s (part %d of %d) on RpcTransport %p: %s",
+                       what, i + 1, niovs, connection->rpcTransport.get(),
                        android::base::HexString(iovs[i].iov_base, iovs[i].iov_len).c_str());
     }
 
@@ -343,7 +344,8 @@
     }
 
     for (int i = 0; i < niovs; i++) {
-        LOG_RPC_DETAIL("Received %s on RpcTransport %p: %s", what, connection->rpcTransport.get(),
+        LOG_RPC_DETAIL("Received %s (part %d of %d) on RpcTransport %p: %s",
+                       what, i + 1, niovs, connection->rpcTransport.get(),
                        android::base::HexString(iovs[i].iov_base, iovs[i].iov_len).c_str());
     }
     return OK;
@@ -660,8 +662,14 @@
 status_t RpcState::drainCommands(const sp<RpcSession::RpcConnection>& connection,
                                  const sp<RpcSession>& session, CommandType type) {
     uint8_t buf;
-    while (connection->rpcTransport->peek(&buf, sizeof(buf)).value_or(0) > 0) {
-        status_t status = getAndExecuteCommand(connection, session, type);
+    while (true) {
+        size_t num_bytes;
+        status_t status = connection->rpcTransport->peek(&buf, sizeof(buf), &num_bytes);
+        if (status == WOULD_BLOCK) break;
+        if (status != OK) return status;
+        if (!num_bytes) break;
+
+        status = getAndExecuteCommand(connection, session, type);
         if (status != OK) return status;
     }
     return OK;
diff --git a/libs/binder/RpcTransportRaw.cpp b/libs/binder/RpcTransportRaw.cpp
index 636e5d0..7cfc780 100644
--- a/libs/binder/RpcTransportRaw.cpp
+++ b/libs/binder/RpcTransportRaw.cpp
@@ -24,9 +24,6 @@
 #include "FdTrigger.h"
 #include "RpcState.h"
 
-using android::base::ErrnoError;
-using android::base::Result;
-
 namespace android {
 
 namespace {
@@ -35,12 +32,20 @@
 class RpcTransportRaw : public RpcTransport {
 public:
     explicit RpcTransportRaw(android::base::unique_fd socket) : mSocket(std::move(socket)) {}
-    Result<size_t> peek(void *buf, size_t size) override {
+    status_t peek(void* buf, size_t size, size_t* out_size) override {
         ssize_t ret = TEMP_FAILURE_RETRY(::recv(mSocket.get(), buf, size, MSG_PEEK));
         if (ret < 0) {
-            return ErrnoError() << "recv(MSG_PEEK)";
+            int savedErrno = errno;
+            if (savedErrno == EAGAIN || savedErrno == EWOULDBLOCK) {
+                return WOULD_BLOCK;
+            }
+
+            LOG_RPC_DETAIL("RpcTransport peek(): %s", strerror(savedErrno));
+            return -savedErrno;
         }
-        return ret;
+
+        *out_size = static_cast<size_t>(ret);
+        return OK;
     }
 
     template <typename SendOrReceive>
diff --git a/libs/binder/RpcTransportTls.cpp b/libs/binder/RpcTransportTls.cpp
index 3936204..bc68c37 100644
--- a/libs/binder/RpcTransportTls.cpp
+++ b/libs/binder/RpcTransportTls.cpp
@@ -37,10 +37,6 @@
 #define LOG_TLS_DETAIL(...) ALOGV(__VA_ARGS__) // for type checking
 #endif
 
-using android::base::ErrnoError;
-using android::base::Error;
-using android::base::Result;
-
 namespace android {
 namespace {
 
@@ -165,17 +161,8 @@
         return ret;
     }
 
-    // |sslError| should be from Ssl::getError().
-    // If |sslError| is WANT_READ / WANT_WRITE, poll for POLLIN / POLLOUT respectively. Otherwise
-    // return error. Also return error if |fdTrigger| is triggered before or during poll().
-    status_t pollForSslError(android::base::borrowed_fd fd, int sslError, FdTrigger* fdTrigger,
-                             const char* fnString, int additionalEvent,
-                             const std::function<status_t()>& altPoll) {
+    status_t toStatus(int sslError, const char* fnString) {
         switch (sslError) {
-            case SSL_ERROR_WANT_READ:
-                return handlePoll(POLLIN | additionalEvent, fd, fdTrigger, fnString, altPoll);
-            case SSL_ERROR_WANT_WRITE:
-                return handlePoll(POLLOUT | additionalEvent, fd, fdTrigger, fnString, altPoll);
             case SSL_ERROR_SYSCALL: {
                 auto queue = toString();
                 LOG_TLS_DETAIL("%s(): %s. Treating as DEAD_OBJECT. Error queue: %s", fnString,
@@ -191,6 +178,22 @@
         }
     }
 
+    // |sslError| should be from Ssl::getError().
+    // If |sslError| is WANT_READ / WANT_WRITE, poll for POLLIN / POLLOUT respectively. Otherwise
+    // return error. Also return error if |fdTrigger| is triggered before or during poll().
+    status_t pollForSslError(android::base::borrowed_fd fd, int sslError, FdTrigger* fdTrigger,
+                             const char* fnString, int additionalEvent,
+                             const std::function<status_t()>& altPoll) {
+        switch (sslError) {
+            case SSL_ERROR_WANT_READ:
+                return handlePoll(POLLIN | additionalEvent, fd, fdTrigger, fnString, altPoll);
+            case SSL_ERROR_WANT_WRITE:
+                return handlePoll(POLLOUT | additionalEvent, fd, fdTrigger, fnString, altPoll);
+            default:
+                return toStatus(sslError, fnString);
+        }
+    }
+
 private:
     bool mHandled = false;
 
@@ -274,7 +277,7 @@
 public:
     RpcTransportTls(android::base::unique_fd socket, Ssl ssl)
           : mSocket(std::move(socket)), mSsl(std::move(ssl)) {}
-    Result<size_t> peek(void* buf, size_t size) override;
+    status_t peek(void* buf, size_t size, size_t* out_size) override;
     status_t interruptableWriteFully(FdTrigger* fdTrigger, iovec* iovs, int niovs,
                                      const std::function<status_t()>& altPoll) override;
     status_t interruptableReadFully(FdTrigger* fdTrigger, iovec* iovs, int niovs,
@@ -286,7 +289,7 @@
 };
 
 // Error code is errno.
-Result<size_t> RpcTransportTls::peek(void* buf, size_t size) {
+status_t RpcTransportTls::peek(void* buf, size_t size, size_t* out_size) {
     size_t todo = std::min<size_t>(size, std::numeric_limits<int>::max());
     auto [ret, errorQueue] = mSsl.call(SSL_peek, buf, static_cast<int>(todo));
     if (ret < 0) {
@@ -294,13 +297,15 @@
         if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
             // Seen EAGAIN / EWOULDBLOCK on recv(2) / send(2).
             // Like RpcTransportRaw::peek(), don't handle it here.
-            return Error(EWOULDBLOCK) << "SSL_peek(): " << errorQueue.toString();
+            errorQueue.clear();
+            return WOULD_BLOCK;
         }
-        return Error() << "SSL_peek(): " << errorQueue.toString();
+        return errorQueue.toStatus(err, "SSL_peek");
     }
     errorQueue.clear();
     LOG_TLS_DETAIL("TLS: Peeked %d bytes!", ret);
-    return ret;
+    *out_size = static_cast<size_t>(ret);
+    return OK;
 }
 
 status_t RpcTransportTls::interruptableWriteFully(FdTrigger* fdTrigger, iovec* iovs, int niovs,
diff --git a/libs/binder/include/binder/RpcServer.h b/libs/binder/include/binder/RpcServer.h
index aaa812b..6b31812 100644
--- a/libs/binder/include/binder/RpcServer.h
+++ b/libs/binder/include/binder/RpcServer.h
@@ -77,7 +77,7 @@
      * have
      */
     [[nodiscard]] status_t setupInetServer(const char* address, unsigned int port,
-                                           unsigned int* assignedPort);
+                                           unsigned int* assignedPort = nullptr);
 
     /**
      * If setup*Server has been successful, return true. Otherwise return false.
diff --git a/libs/binder/include/binder/RpcTransport.h b/libs/binder/include/binder/RpcTransport.h
index ade2d94..751c4f9 100644
--- a/libs/binder/include/binder/RpcTransport.h
+++ b/libs/binder/include/binder/RpcTransport.h
@@ -22,7 +22,6 @@
 #include <memory>
 #include <string>
 
-#include <android-base/result.h>
 #include <android-base/unique_fd.h>
 #include <utils/Errors.h>
 
@@ -41,7 +40,7 @@
     virtual ~RpcTransport() = default;
 
     // replacement of ::recv(MSG_PEEK). Error code may not be set if TLS is enabled.
-    [[nodiscard]] virtual android::base::Result<size_t> peek(void *buf, size_t size) = 0;
+    [[nodiscard]] virtual status_t peek(void *buf, size_t size, size_t *out_size) = 0;
 
     /**
      * Read (or write), but allow to be interrupted by a trigger.
diff --git a/libs/binder/include_activitymanager/binder/ActivityManager.h b/libs/binder/include_activitymanager/binder/ActivityManager.h
index abc7f1d..5dfbd44 100644
--- a/libs/binder/include_activitymanager/binder/ActivityManager.h
+++ b/libs/binder/include_activitymanager/binder/ActivityManager.h
@@ -31,20 +31,21 @@
 class ActivityManager
 {
 public:
-
     enum {
         // Flag for registerUidObserver: report uid state changed
-        UID_OBSERVER_PROCSTATE = 1<<0,
+        UID_OBSERVER_PROCSTATE = 1 << 0,
         // Flag for registerUidObserver: report uid gone
-        UID_OBSERVER_GONE = 1<<1,
+        UID_OBSERVER_GONE = 1 << 1,
         // Flag for registerUidObserver: report uid has become idle
-        UID_OBSERVER_IDLE = 1<<2,
+        UID_OBSERVER_IDLE = 1 << 2,
         // Flag for registerUidObserver: report uid has become active
-        UID_OBSERVER_ACTIVE = 1<<3,
+        UID_OBSERVER_ACTIVE = 1 << 3,
         // Flag for registerUidObserver: report uid cached state has changed
-        UID_OBSERVER_CACHED = 1<<4,
+        UID_OBSERVER_CACHED = 1 << 4,
         // Flag for registerUidObserver: report uid capability has changed
-        UID_OBSERVER_CAPABILITY = 1<<5,
+        UID_OBSERVER_CAPABILITY = 1 << 5,
+        // Flag for registerUidObserver: report pid oom adj has changed
+        UID_OBSERVER_PROC_OOM_ADJ = 1 << 6,
     };
 
     // PROCESS_STATE_* must come from frameworks/base/core/java/android/app/ProcessStateEnum.aidl.
diff --git a/libs/binder/include_activitymanager/binder/IUidObserver.h b/libs/binder/include_activitymanager/binder/IUidObserver.h
index 9291c0b..17f03a9 100644
--- a/libs/binder/include_activitymanager/binder/IUidObserver.h
+++ b/libs/binder/include_activitymanager/binder/IUidObserver.h
@@ -33,13 +33,15 @@
     virtual void onUidActive(uid_t uid) = 0;
     virtual void onUidIdle(uid_t uid, bool disabled) = 0;
     virtual void onUidStateChanged(uid_t uid, int32_t procState, int64_t procStateSeq,
-            int32_t capability) = 0;
+                                   int32_t capability) = 0;
+    virtual void onUidProcAdjChanged(uid_t uid) = 0;
 
     enum {
         ON_UID_GONE_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
         ON_UID_ACTIVE_TRANSACTION,
         ON_UID_IDLE_TRANSACTION,
-        ON_UID_STATE_CHANGED_TRANSACTION
+        ON_UID_STATE_CHANGED_TRANSACTION,
+        ON_UID_PROC_ADJ_CHANGED_TRANSACTION
     };
 };
 
diff --git a/libs/binder/ndk/include_cpp/android/binder_interface_utils.h b/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
index b3bc7f4..c8e78fc 100644
--- a/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
+++ b/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
@@ -324,4 +324,42 @@
 
 }  // namespace ndk
 
+// Once minSdkVersion is 30, we are guaranteed to be building with the
+// Android 11 AIDL compiler which supports the SharedRefBase::make API.
+#if !defined(__ANDROID_API__) || __ANDROID_API__ >= 30 || defined(__ANDROID_APEX__)
+namespace ndk::internal {
+template <typename T, typename = void>
+struct is_complete_type : std::false_type {};
+
+template <typename T>
+struct is_complete_type<T, decltype(void(sizeof(T)))> : std::true_type {};
+}  // namespace ndk::internal
+
+namespace std {
+
+// Define `SharedRefBase` specific versions of `std::make_shared` and
+// `std::make_unique` to block people from using them. Using them to allocate
+// `ndk::SharedRefBase` objects results in double ownership. Use
+// `ndk::SharedRefBase::make<T>(...)` instead.
+//
+// Note: We exclude incomplete types because `std::is_base_of` is undefined in
+// that case.
+
+template <typename T, typename... Args,
+          std::enable_if_t<ndk::internal::is_complete_type<T>::value, bool> = true,
+          std::enable_if_t<std::is_base_of<ndk::SharedRefBase, T>::value, bool> = true>
+shared_ptr<T> make_shared(Args...) {  // SEE COMMENT ABOVE.
+    static_assert(!std::is_base_of<ndk::SharedRefBase, T>::value);
+}
+
+template <typename T, typename... Args,
+          std::enable_if_t<ndk::internal::is_complete_type<T>::value, bool> = true,
+          std::enable_if_t<std::is_base_of<ndk::SharedRefBase, T>::value, bool> = true>
+unique_ptr<T> make_unique(Args...) {  // SEE COMMENT ABOVE.
+    static_assert(!std::is_base_of<ndk::SharedRefBase, T>::value);
+}
+
+}  // namespace std
+#endif
+
 /** @} */
diff --git a/libs/binder/ndk/include_platform/android/binder_stability.h b/libs/binder/ndk/include_platform/android/binder_stability.h
index f113ba8..d0cd11f 100644
--- a/libs/binder/ndk/include_platform/android/binder_stability.h
+++ b/libs/binder/ndk/include_platform/android/binder_stability.h
@@ -97,6 +97,10 @@
  *
  * This interface has system<->vendor stability
  */
+// b/227835797 - can't use __INTRODUCED_IN(30) because old targets load this code
+#if __ANDROID_MIN_SDK_VERSION__ < 30
+__attribute__((weak))
+#endif  // __ANDROID_MIN_SDK_VERSION__ < 30
 void AIBinder_markVintfStability(AIBinder* binder);
 
 __END_DECLS
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index 357b454..1b136dc 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -231,8 +231,7 @@
 }
 
 TEST(NdkBinder, DetectNoSharedRefBaseCreated) {
-    EXPECT_DEATH(std::make_shared<MyBinderNdkUnitTest>(),
-                 "SharedRefBase: no ref created during lifetime");
+    EXPECT_DEATH(MyBinderNdkUnitTest(), "SharedRefBase: no ref created during lifetime");
 }
 
 TEST(NdkBinder, GetServiceThatDoesntExist) {
diff --git a/libs/binder/rust/src/proxy.rs b/libs/binder/rust/src/proxy.rs
index e3e4730..4df557b 100644
--- a/libs/binder/rust/src/proxy.rs
+++ b/libs/binder/rust/src/proxy.rs
@@ -575,6 +575,20 @@
     cookie_decr_refcount: unsafe extern "C" fn(*mut c_void),
 }
 
+/// # Safety
+///
+/// A `DeathRecipient` is a wrapper around `AIBinder_DeathRecipient` and a pointer
+/// to a `Fn` which is `Sync` and `Send` (the cookie field). As
+/// `AIBinder_DeathRecipient` is threadsafe, this structure is too.
+unsafe impl Send for DeathRecipient {}
+
+/// # Safety
+///
+/// A `DeathRecipient` is a wrapper around `AIBinder_DeathRecipient` and a pointer
+/// to a `Fn` which is `Sync` and `Send` (the cookie field). As
+/// `AIBinder_DeathRecipient` is threadsafe, this structure is too.
+unsafe impl Sync for DeathRecipient {}
+
 impl DeathRecipient {
     /// Create a new death recipient that will call the given callback when its
     /// associated object dies.
diff --git a/libs/binder/tests/binderBinderUnitTest.cpp b/libs/binder/tests/binderBinderUnitTest.cpp
index 1be0c59..ce2770f 100644
--- a/libs/binder/tests/binderBinderUnitTest.cpp
+++ b/libs/binder/tests/binderBinderUnitTest.cpp
@@ -41,3 +41,10 @@
     EXPECT_EQ(kObject1, binder->detachObject(kObjectId1));
     EXPECT_EQ(nullptr, binder->attachObject(kObjectId1, kObject2, nullptr, nullptr));
 }
+
+TEST(Binder, AttachExtension) {
+    auto binder = sp<BBinder>::make();
+    auto ext = sp<BBinder>::make();
+    binder->setExtension(ext);
+    EXPECT_EQ(ext, binder->getExtension());
+}
diff --git a/libs/binder/tests/include_tls_test_utils/binder/RpcTlsTestUtils.h b/libs/binder/tests/include_tls_test_utils/binder/RpcTlsTestUtils.h
index 094addd..50d12c4 100644
--- a/libs/binder/tests/include_tls_test_utils/binder/RpcTlsTestUtils.h
+++ b/libs/binder/tests/include_tls_test_utils/binder/RpcTlsTestUtils.h
@@ -18,6 +18,7 @@
 
 #include <memory>
 #include <mutex>
+#include <vector>
 
 #include <binder/RpcAuth.h>
 #include <binder/RpcCertificateFormat.h>
diff --git a/libs/binder/tests/parcel_fuzzer/Android.bp b/libs/binder/tests/parcel_fuzzer/Android.bp
index 57d496d..e5d32da 100644
--- a/libs/binder/tests/parcel_fuzzer/Android.bp
+++ b/libs/binder/tests/parcel_fuzzer/Android.bp
@@ -29,7 +29,6 @@
         "libcutils",
         "libhidlbase",
         "liblog",
-        "libutils",
     ],
 
     target: {
@@ -37,12 +36,14 @@
             shared_libs: [
                 "libbinder_ndk",
                 "libbinder",
+                "libutils",
             ],
         },
         host: {
             static_libs: [
                 "libbinder_ndk",
                 "libbinder",
+                "libutils",
             ],
         },
         darwin: {
diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp
index bc2eb23..c010a2e 100644
--- a/libs/ftl/Android.bp
+++ b/libs/ftl/Android.bp
@@ -14,10 +14,11 @@
         address: true,
     },
     srcs: [
-        "Flags_test.cpp",
         "cast_test.cpp",
         "concat_test.cpp",
         "enum_test.cpp",
+        "fake_guard_test.cpp",
+        "flags_test.cpp",
         "future_test.cpp",
         "small_map_test.cpp",
         "small_vector_test.cpp",
@@ -29,13 +30,6 @@
         "-Werror",
         "-Wextra",
         "-Wpedantic",
-    ],
-
-    header_libs: [
-        "libbase_headers",
-    ],
-
-    shared_libs: [
-        "libbase",
+        "-Wthread-safety",
     ],
 }
diff --git a/libs/ftl/enum_test.cpp b/libs/ftl/enum_test.cpp
index d8ce7a5..5592a01 100644
--- a/libs/ftl/enum_test.cpp
+++ b/libs/ftl/enum_test.cpp
@@ -143,6 +143,16 @@
     EXPECT_EQ(ftl::flag_string(Flags::kNone), "0b0");
     EXPECT_EQ(ftl::flag_string(Flags::kMask), "0b10010010");
     EXPECT_EQ(ftl::flag_string(Flags::kAll), "0b11111111");
+
+    enum class Flags64 : std::uint64_t {
+      kFlag0 = 0b1ull,
+      kFlag63 = 0x8000'0000'0000'0000ull,
+      kMask = kFlag0 | kFlag63
+    };
+
+    EXPECT_EQ(ftl::flag_string(Flags64::kFlag0), "kFlag0");
+    EXPECT_EQ(ftl::flag_string(Flags64::kFlag63), "kFlag63");
+    EXPECT_EQ(ftl::flag_string(Flags64::kMask), "0x8000000000000001");
   }
   {
     EXPECT_EQ(ftl::enum_string(Planet::kEarth), "kEarth");
diff --git a/libs/ftl/fake_guard_test.cpp b/libs/ftl/fake_guard_test.cpp
new file mode 100644
index 0000000..9d36e69
--- /dev/null
+++ b/libs/ftl/fake_guard_test.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ftl/fake_guard.h>
+#include <gtest/gtest.h>
+
+#include <functional>
+#include <mutex>
+
+namespace android::test {
+
+// Keep in sync with example usage in header file.
+TEST(FakeGuard, Example) {
+  struct {
+    std::mutex mutex;
+    int x FTL_ATTRIBUTE(guarded_by(mutex)) = -1;
+
+    int f() {
+      {
+        ftl::FakeGuard guard(mutex);
+        x = 0;
+      }
+
+      return FTL_FAKE_GUARD(mutex, x + 1);
+    }
+
+    std::function<int()> g() const {
+      return [this]() FTL_FAKE_GUARD(mutex) { return x; };
+    }
+  } s;
+
+  EXPECT_EQ(s.f(), 1);
+  EXPECT_EQ(s.g()(), 0);
+}
+
+}  // namespace android::test
diff --git a/libs/ftl/Flags_test.cpp b/libs/ftl/flags_test.cpp
similarity index 98%
rename from libs/ftl/Flags_test.cpp
rename to libs/ftl/flags_test.cpp
index d241fa2..eea052b 100644
--- a/libs/ftl/Flags_test.cpp
+++ b/libs/ftl/flags_test.cpp
@@ -14,14 +14,15 @@
  * limitations under the License.
  */
 
+#include <ftl/flags.h>
 #include <gtest/gtest.h>
-#include <ftl/Flags.h>
 
 #include <type_traits>
 
 namespace android::test {
 
-using namespace android::flag_operators;
+using ftl::Flags;
+using namespace ftl::flag_operators;
 
 enum class TestFlags : uint8_t { ONE = 0x1, TWO = 0x2, THREE = 0x4 };
 
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index 5532c6e..24d39fe 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -226,101 +226,6 @@
         return result;
     }
 
-    sp<IBinder> createDisplay(const String8& displayName, bool secure) override {
-        Parcel data, reply;
-        data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        status_t status = data.writeString8(displayName);
-        if (status) {
-            return nullptr;
-        }
-        status = data.writeBool(secure);
-        if (status) {
-            return nullptr;
-        }
-
-        status = remote()->transact(BnSurfaceComposer::CREATE_DISPLAY, data, &reply);
-        if (status) {
-            return nullptr;
-        }
-        sp<IBinder> display;
-        status = reply.readNullableStrongBinder(&display);
-        if (status) {
-            return nullptr;
-        }
-        return display;
-    }
-
-    void destroyDisplay(const sp<IBinder>& display) override {
-        Parcel data, reply;
-        data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        data.writeStrongBinder(display);
-        remote()->transact(BnSurfaceComposer::DESTROY_DISPLAY, data, &reply);
-    }
-
-    std::vector<PhysicalDisplayId> getPhysicalDisplayIds() const override {
-        Parcel data, reply;
-        data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        if (remote()->transact(BnSurfaceComposer::GET_PHYSICAL_DISPLAY_IDS, data, &reply) ==
-            NO_ERROR) {
-            std::vector<uint64_t> rawIds;
-            if (reply.readUint64Vector(&rawIds) == NO_ERROR) {
-                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;
-            }
-        }
-
-        return {};
-    }
-
-    status_t getPrimaryPhysicalDisplayId(PhysicalDisplayId* displayId) const override {
-        Parcel data, reply;
-        SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor());
-        SAFE_PARCEL(remote()->transact, BnSurfaceComposer::GET_PRIMARY_PHYSICAL_DISPLAY_ID, data,
-                    &reply);
-        uint64_t rawId;
-        SAFE_PARCEL(reply.readUint64, &rawId);
-        if (const auto id = DisplayId::fromValue<PhysicalDisplayId>(rawId)) {
-            *displayId = *id;
-            return NO_ERROR;
-        }
-        return NAME_NOT_FOUND;
-    }
-
-    sp<IBinder> getPhysicalDisplayToken(PhysicalDisplayId displayId) const override {
-        Parcel data, reply;
-        data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        data.writeUint64(displayId.value);
-        remote()->transact(BnSurfaceComposer::GET_PHYSICAL_DISPLAY_TOKEN, data, &reply);
-        return reply.readStrongBinder();
-    }
-
-    void setPowerMode(const sp<IBinder>& display, int mode) override {
-        Parcel data, reply;
-        data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        data.writeStrongBinder(display);
-        data.writeInt32(mode);
-        remote()->transact(BnSurfaceComposer::SET_POWER_MODE, data, &reply);
-    }
-
-    status_t getDisplayState(const sp<IBinder>& display, ui::DisplayState* state) override {
-        Parcel data, reply;
-        data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        data.writeStrongBinder(display);
-        remote()->transact(BnSurfaceComposer::GET_DISPLAY_STATE, data, &reply);
-        const status_t result = reply.readInt32();
-        if (result == NO_ERROR) {
-            memcpy(state, reply.readInplace(sizeof(ui::DisplayState)), sizeof(ui::DisplayState));
-        }
-        return result;
-    }
-
     status_t getStaticDisplayInfo(const sp<IBinder>& display,
                                   ui::StaticDisplayInfo* info) override {
         Parcel data, reply;
@@ -343,20 +248,6 @@
         return reply.read(*info);
     }
 
-    status_t getDisplayStats(const sp<IBinder>& display, DisplayStatInfo* stats) override {
-        Parcel data, reply;
-        data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        data.writeStrongBinder(display);
-        remote()->transact(BnSurfaceComposer::GET_DISPLAY_STATS, data, &reply);
-        status_t result = reply.readInt32();
-        if (result == NO_ERROR) {
-            memcpy(stats,
-                    reply.readInplace(sizeof(DisplayStatInfo)),
-                    sizeof(DisplayStatInfo));
-        }
-        return result;
-    }
-
     status_t getDisplayNativePrimaries(const sp<IBinder>& display,
                                        ui::DisplayPrimaries& primaries) override {
         Parcel data, reply;
@@ -408,29 +299,6 @@
         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;
@@ -456,73 +324,6 @@
         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());
-        if (result != NO_ERROR) {
-            ALOGE("setAutoLowLatencyMode failed to writeInterfaceToken: %d", result);
-            return;
-        }
-
-        result = data.writeStrongBinder(display);
-        if (result != NO_ERROR) {
-            ALOGE("setAutoLowLatencyMode failed to writeStrongBinder: %d", result);
-            return;
-        }
-        result = data.writeBool(on);
-        if (result != NO_ERROR) {
-            ALOGE("setAutoLowLatencyMode failed to writeBool: %d", result);
-            return;
-        }
-        result = remote()->transact(BnSurfaceComposer::SET_AUTO_LOW_LATENCY_MODE, data, &reply);
-        if (result != NO_ERROR) {
-            ALOGE("setAutoLowLatencyMode failed to transact: %d", result);
-            return;
-        }
-    }
-
-    void setGameContentType(const sp<IBinder>& display, bool on) override {
-        Parcel data, reply;
-        status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        if (result != NO_ERROR) {
-            ALOGE("setGameContentType failed to writeInterfaceToken: %d", result);
-            return;
-        }
-        result = data.writeStrongBinder(display);
-        if (result != NO_ERROR) {
-            ALOGE("setGameContentType failed to writeStrongBinder: %d", result);
-            return;
-        }
-        result = data.writeBool(on);
-        if (result != NO_ERROR) {
-            ALOGE("setGameContentType failed to writeBool: %d", result);
-            return;
-        }
-        result = remote()->transact(BnSurfaceComposer::SET_GAME_CONTENT_TYPE, data, &reply);
-        if (result != NO_ERROR) {
-            ALOGE("setGameContentType failed to transact: %d", result);
-        }
-    }
-
     status_t clearAnimationFrameStats() override {
         Parcel data, reply;
         status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
@@ -797,26 +598,6 @@
         return error;
     }
 
-    status_t isWideColorDisplay(const sp<IBinder>& token,
-                                bool* outIsWideColorDisplay) const override {
-        Parcel data, reply;
-        status_t error = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        if (error != NO_ERROR) {
-            return error;
-        }
-        error = data.writeStrongBinder(token);
-        if (error != NO_ERROR) {
-            return error;
-        }
-
-        error = remote()->transact(BnSurfaceComposer::IS_WIDE_COLOR_DISPLAY, data, &reply);
-        if (error != NO_ERROR) {
-            return error;
-        }
-        error = reply.readBool(outIsWideColorDisplay);
-        return error;
-    }
-
     status_t addRegionSamplingListener(const Rect& samplingArea, const sp<IBinder>& stopLayerHandle,
                                        const sp<IRegionSamplingListener>& listener) override {
         Parcel data, reply;
@@ -1049,109 +830,6 @@
         return reply.readInt32();
     }
 
-    status_t getDisplayBrightnessSupport(const sp<IBinder>& displayToken,
-                                         bool* outSupport) const override {
-        Parcel data, reply;
-        status_t error = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        if (error != NO_ERROR) {
-            ALOGE("getDisplayBrightnessSupport: failed to write interface token: %d", error);
-            return error;
-        }
-        error = data.writeStrongBinder(displayToken);
-        if (error != NO_ERROR) {
-            ALOGE("getDisplayBrightnessSupport: failed to write display token: %d", error);
-            return error;
-        }
-        error = remote()->transact(BnSurfaceComposer::GET_DISPLAY_BRIGHTNESS_SUPPORT, data, &reply);
-        if (error != NO_ERROR) {
-            ALOGE("getDisplayBrightnessSupport: failed to transact: %d", error);
-            return error;
-        }
-        bool support;
-        error = reply.readBool(&support);
-        if (error != NO_ERROR) {
-            ALOGE("getDisplayBrightnessSupport: failed to read support: %d", error);
-            return error;
-        }
-        *outSupport = support;
-        return NO_ERROR;
-    }
-
-    status_t setDisplayBrightness(const sp<IBinder>& displayToken,
-                                  const gui::DisplayBrightness& brightness) override {
-        Parcel data, reply;
-        status_t error = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        if (error != NO_ERROR) {
-            ALOGE("setDisplayBrightness: failed to write interface token: %d", error);
-            return error;
-        }
-        error = data.writeStrongBinder(displayToken);
-        if (error != NO_ERROR) {
-            ALOGE("setDisplayBrightness: failed to write display token: %d", error);
-            return error;
-        }
-        error = data.writeParcelable(brightness);
-        if (error != NO_ERROR) {
-            ALOGE("setDisplayBrightness: failed to write brightness: %d", error);
-            return error;
-        }
-        error = remote()->transact(BnSurfaceComposer::SET_DISPLAY_BRIGHTNESS, data, &reply);
-        if (error != NO_ERROR) {
-            ALOGE("setDisplayBrightness: failed to transact: %d", error);
-            return error;
-        }
-        return NO_ERROR;
-    }
-
-    status_t addHdrLayerInfoListener(const sp<IBinder>& displayToken,
-                                     const sp<gui::IHdrLayerInfoListener>& listener) override {
-        Parcel data, reply;
-        SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor());
-        SAFE_PARCEL(data.writeStrongBinder, displayToken);
-        SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(listener));
-        const status_t error =
-                remote()->transact(BnSurfaceComposer::ADD_HDR_LAYER_INFO_LISTENER, data, &reply);
-        if (error != OK) {
-            ALOGE("addHdrLayerInfoListener: Failed to transact; error = %d", error);
-        }
-        return error;
-    }
-
-    status_t removeHdrLayerInfoListener(const sp<IBinder>& displayToken,
-                                        const sp<gui::IHdrLayerInfoListener>& listener) override {
-        Parcel data, reply;
-        SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor());
-        SAFE_PARCEL(data.writeStrongBinder, displayToken);
-        SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(listener));
-        const status_t error =
-                remote()->transact(BnSurfaceComposer::REMOVE_HDR_LAYER_INFO_LISTENER, data, &reply);
-        if (error != OK) {
-            ALOGE("removeHdrLayerInfoListener: Failed to transact; error = %d", error);
-        }
-        return error;
-    }
-
-    status_t notifyPowerBoost(int32_t boostId) override {
-        Parcel data, reply;
-        status_t error = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        if (error != NO_ERROR) {
-            ALOGE("notifyPowerBoost: failed to write interface token: %d", error);
-            return error;
-        }
-        error = data.writeInt32(boostId);
-        if (error != NO_ERROR) {
-            ALOGE("notifyPowerBoost: failed to write boostId: %d", error);
-            return error;
-        }
-        error = remote()->transact(BnSurfaceComposer::NOTIFY_POWER_BOOST, data, &reply,
-                                   IBinder::FLAG_ONEWAY);
-        if (error != NO_ERROR) {
-            ALOGE("notifyPowerBoost: failed to transact: %d", error);
-            return error;
-        }
-        return NO_ERROR;
-    }
-
     status_t setGlobalShadowSettings(const half4& ambientColor, const half4& spotColor,
                                      float lightPosY, float lightPosZ, float lightRadius) override {
         Parcel data, reply;
@@ -1467,41 +1145,6 @@
             reply->writeStrongBinder(IInterface::asBinder(connection));
             return NO_ERROR;
         }
-        case CREATE_DISPLAY: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            String8 displayName;
-            SAFE_PARCEL(data.readString8, &displayName);
-            bool secure = false;
-            SAFE_PARCEL(data.readBool, &secure);
-            sp<IBinder> display = createDisplay(displayName, secure);
-            SAFE_PARCEL(reply->writeStrongBinder, display);
-            return NO_ERROR;
-        }
-        case DESTROY_DISPLAY: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            sp<IBinder> display = data.readStrongBinder();
-            destroyDisplay(display);
-            return NO_ERROR;
-        }
-        case GET_PHYSICAL_DISPLAY_TOKEN: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            const auto id = DisplayId::fromValue<PhysicalDisplayId>(data.readUint64());
-            if (!id) return BAD_VALUE;
-            reply->writeStrongBinder(getPhysicalDisplayToken(*id));
-            return NO_ERROR;
-        }
-        case GET_DISPLAY_STATE: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            ui::DisplayState state;
-            const sp<IBinder> display = data.readStrongBinder();
-            const status_t result = getDisplayState(display, &state);
-            reply->writeInt32(result);
-            if (result == NO_ERROR) {
-                memcpy(reply->writeInplace(sizeof(ui::DisplayState)), &state,
-                       sizeof(ui::DisplayState));
-            }
-            return NO_ERROR;
-        }
         case GET_STATIC_DISPLAY_INFO: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
             ui::StaticDisplayInfo info;
@@ -1522,18 +1165,6 @@
             SAFE_PARCEL(reply->write, info);
             return NO_ERROR;
         }
-        case GET_DISPLAY_STATS: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            DisplayStatInfo stats;
-            sp<IBinder> display = data.readStrongBinder();
-            status_t result = getDisplayStats(display, &stats);
-            reply->writeInt32(result);
-            if (result == NO_ERROR) {
-                memcpy(reply->writeInplace(sizeof(DisplayStatInfo)),
-                        &stats, sizeof(DisplayStatInfo));
-            }
-            return NO_ERROR;
-        }
         case GET_DISPLAY_NATIVE_PRIMARIES: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
             ui::DisplayPrimaries primaries;
@@ -1573,15 +1204,6 @@
             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;
@@ -1598,50 +1220,6 @@
             }
             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;
-            status_t result = data.readStrongBinder(&display);
-            if (result != NO_ERROR) {
-                ALOGE("setAutoLowLatencyMode failed to readStrongBinder: %d", result);
-                return result;
-            }
-            bool setAllm = false;
-            result = data.readBool(&setAllm);
-            if (result != NO_ERROR) {
-                ALOGE("setAutoLowLatencyMode failed to readBool: %d", result);
-                return result;
-            }
-            setAutoLowLatencyMode(display, setAllm);
-            return result;
-        }
-        case SET_GAME_CONTENT_TYPE: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            sp<IBinder> display = nullptr;
-            status_t result = data.readStrongBinder(&display);
-            if (result != NO_ERROR) {
-                ALOGE("setGameContentType failed to readStrongBinder: %d", result);
-                return result;
-            }
-            bool setGameContentTypeOn = false;
-            result = data.readBool(&setGameContentTypeOn);
-            if (result != NO_ERROR) {
-                ALOGE("setGameContentType failed to readBool: %d", result);
-                return result;
-            }
-            setGameContentType(display, setGameContentTypeOn);
-            return result;
-        }
         case CLEAR_ANIMATION_FRAME_STATS: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
             status_t result = clearAnimationFrameStats();
@@ -1656,13 +1234,6 @@
             reply->writeInt32(result);
             return NO_ERROR;
         }
-        case SET_POWER_MODE: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            sp<IBinder> display = data.readStrongBinder();
-            int32_t mode = data.readInt32();
-            setPowerMode(display, mode);
-            return NO_ERROR;
-        }
         case ENABLE_VSYNC_INJECTIONS: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
             bool enable = false;
@@ -1812,38 +1383,6 @@
             }
             return error;
         }
-        case IS_WIDE_COLOR_DISPLAY: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            sp<IBinder> display = nullptr;
-            status_t error = data.readStrongBinder(&display);
-            if (error != NO_ERROR) {
-                return error;
-            }
-            bool result;
-            error = isWideColorDisplay(display, &result);
-            if (error == NO_ERROR) {
-                reply->writeBool(result);
-            }
-            return error;
-        }
-        case GET_PHYSICAL_DISPLAY_IDS: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            std::vector<PhysicalDisplayId> ids = getPhysicalDisplayIds();
-            std::vector<uint64_t> rawIds(ids.size());
-            std::transform(ids.begin(), ids.end(), rawIds.begin(),
-                           [](PhysicalDisplayId id) { return id.value; });
-            return reply->writeUint64Vector(rawIds);
-        }
-        case GET_PRIMARY_PHYSICAL_DISPLAY_ID: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            PhysicalDisplayId id;
-            status_t result = getPrimaryPhysicalDisplayId(&id);
-            if (result != NO_ERROR) {
-                ALOGE("getPrimaryPhysicalDisplayId: Failed to get id");
-                return result;
-            }
-            return reply->writeUint64(id.value);
-        }
         case ADD_REGION_SAMPLING_LISTENER: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
             Rect samplingArea;
@@ -2041,77 +1580,6 @@
             reply->writeInt32(result);
             return result;
         }
-        case GET_DISPLAY_BRIGHTNESS_SUPPORT: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            sp<IBinder> displayToken;
-            status_t error = data.readNullableStrongBinder(&displayToken);
-            if (error != NO_ERROR) {
-                ALOGE("getDisplayBrightnessSupport: failed to read display token: %d", error);
-                return error;
-            }
-            bool support = false;
-            error = getDisplayBrightnessSupport(displayToken, &support);
-            reply->writeBool(support);
-            return error;
-        }
-        case SET_DISPLAY_BRIGHTNESS: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            sp<IBinder> displayToken;
-            status_t error = data.readNullableStrongBinder(&displayToken);
-            if (error != NO_ERROR) {
-                ALOGE("setDisplayBrightness: failed to read display token: %d", error);
-                return error;
-            }
-            gui::DisplayBrightness brightness;
-            error = data.readParcelable(&brightness);
-            if (error != NO_ERROR) {
-                ALOGE("setDisplayBrightness: failed to read brightness: %d", error);
-                return error;
-            }
-            return setDisplayBrightness(displayToken, brightness);
-        }
-        case ADD_HDR_LAYER_INFO_LISTENER: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            sp<IBinder> displayToken;
-            status_t error = data.readNullableStrongBinder(&displayToken);
-            if (error != NO_ERROR) {
-                ALOGE("addHdrLayerInfoListener: Failed to read display token");
-                return error;
-            }
-            sp<gui::IHdrLayerInfoListener> listener;
-            error = data.readNullableStrongBinder(&listener);
-            if (error != NO_ERROR) {
-                ALOGE("addHdrLayerInfoListener: Failed to read listener");
-                return error;
-            }
-            return addHdrLayerInfoListener(displayToken, listener);
-        }
-        case REMOVE_HDR_LAYER_INFO_LISTENER: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            sp<IBinder> displayToken;
-            status_t error = data.readNullableStrongBinder(&displayToken);
-            if (error != NO_ERROR) {
-                ALOGE("removeHdrLayerInfoListener: Failed to read display token");
-                return error;
-            }
-            sp<gui::IHdrLayerInfoListener> listener;
-            error = data.readNullableStrongBinder(&listener);
-            if (error != NO_ERROR) {
-                ALOGE("removeHdrLayerInfoListener: Failed to read listener");
-                return error;
-            }
-            return removeHdrLayerInfoListener(displayToken, listener);
-        }
-        case NOTIFY_POWER_BOOST: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            int32_t boostId;
-            status_t error = data.readInt32(&boostId);
-            if (error != NO_ERROR) {
-                ALOGE("notifyPowerBoost: failed to read boostId: %d", error);
-                return error;
-            }
-            return notifyPowerBoost(boostId);
-        }
         case SET_GLOBAL_SHADOW_SETTINGS: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
 
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 34db5b1..502031c 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -16,8 +16,8 @@
 
 #define LOG_TAG "LayerState"
 
-#include <apex/window.h>
-#include <inttypes.h>
+#include <cinttypes>
+#include <cmath>
 
 #include <android/native_window.h>
 #include <binder/Parcel.h>
@@ -25,10 +25,9 @@
 #include <gui/ISurfaceComposerClient.h>
 #include <gui/LayerState.h>
 #include <private/gui/ParcelUtils.h>
+#include <system/window.h>
 #include <utils/Errors.h>
 
-#include <cmath>
-
 namespace android {
 
 using gui::FocusRequest;
@@ -134,6 +133,7 @@
     SAFE_PARCEL(output.writeByte, changeFrameRateStrategy);
     SAFE_PARCEL(output.writeUint32, fixedTransformHint);
     SAFE_PARCEL(output.writeBool, autoRefresh);
+    SAFE_PARCEL(output.writeBool, dimmingEnabled);
 
     SAFE_PARCEL(output.writeUint32, blurRegions.size());
     for (auto region : blurRegions) {
@@ -243,6 +243,7 @@
     SAFE_PARCEL(input.readUint32, &tmpUint32);
     fixedTransformHint = static_cast<ui::Transform::RotationFlags>(tmpUint32);
     SAFE_PARCEL(input.readBool, &autoRefresh);
+    SAFE_PARCEL(input.readBool, &dimmingEnabled);
 
     uint32_t numRegions = 0;
     SAFE_PARCEL(input.readUint32, &numRegions);
@@ -598,6 +599,10 @@
         what |= eColorSpaceAgnosticChanged;
         colorSpaceAgnostic = other.colorSpaceAgnostic;
     }
+    if (other.what & eDimmingEnabledChanged) {
+        what |= eDimmingEnabledChanged;
+        dimmingEnabled = other.dimmingEnabled;
+    }
     if ((other.what & what) != other.what) {
         ALOGE("Unmerged SurfaceComposer Transaction properties. LayerState::merge needs updating? "
               "other.what=0x%" PRIX64 " what=0x%" PRIX64 " unmerged flags=0x%" PRIX64,
@@ -673,7 +678,9 @@
 
     if (compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT &&
         compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE &&
-        (!privileged || compatibility != ANATIVEWINDOW_FRAME_RATE_EXACT)) {
+        (!privileged ||
+         (compatibility != ANATIVEWINDOW_FRAME_RATE_EXACT &&
+          compatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE))) {
         ALOGE("%s failed - invalid compatibility value %d privileged: %s", functionName,
               compatibility, privileged ? "yes" : "no");
         return false;
@@ -815,7 +822,7 @@
 status_t BufferData::readFromParcel(const Parcel* input) {
     int32_t tmpInt32;
     SAFE_PARCEL(input->readInt32, &tmpInt32);
-    flags = Flags<BufferDataChange>(tmpInt32);
+    flags = ftl::Flags<BufferDataChange>(tmpInt32);
 
     bool tmpBool = false;
     SAFE_PARCEL(input->readBool, &tmpBool);
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 20c4146..54b6d6a 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -27,13 +27,13 @@
 
 #include <inttypes.h>
 
+#include <android/gui/DisplayStatInfo.h>
 #include <android/native_window.h>
 
 #include <utils/Log.h>
 #include <utils/Trace.h>
 #include <utils/NativeHandle.h>
 
-#include <ui/DisplayStatInfo.h>
 #include <ui/DynamicDisplayInfo.h>
 #include <ui/Fence.h>
 #include <ui/GraphicBuffer.h>
@@ -45,6 +45,7 @@
 #include <gui/ISurfaceComposer.h>
 #include <gui/LayerState.h>
 #include <private/gui/ComposerService.h>
+#include <private/gui/ComposerServiceAIDL.h>
 
 namespace android {
 
@@ -125,6 +126,10 @@
     return ComposerService::getComposerService();
 }
 
+sp<gui::ISurfaceComposer> Surface::composerServiceAIDL() const {
+    return ComposerServiceAIDL::getComposerService();
+}
+
 nsecs_t Surface::now() const {
     return systemTime();
 }
@@ -174,10 +179,10 @@
 status_t Surface::getDisplayRefreshCycleDuration(nsecs_t* outRefreshDuration) {
     ATRACE_CALL();
 
-    DisplayStatInfo stats;
-    status_t result = composerService()->getDisplayStats(nullptr, &stats);
-    if (result != NO_ERROR) {
-        return result;
+    gui::DisplayStatInfo stats;
+    binder::Status status = composerServiceAIDL()->getDisplayStats(nullptr, &stats);
+    if (!status.isOk()) {
+        return status.transactionError();
     }
 
     *outRefreshDuration = stats.vsyncPeriod;
@@ -343,20 +348,20 @@
 status_t Surface::getWideColorSupport(bool* supported) {
     ATRACE_CALL();
 
-    const sp<IBinder> display = composerService()->getInternalDisplayToken();
+    const sp<IBinder> display = ComposerServiceAIDL::getInstance().getInternalDisplayToken();
     if (display == nullptr) {
         return NAME_NOT_FOUND;
     }
 
     *supported = false;
-    status_t error = composerService()->isWideColorDisplay(display, supported);
-    return error;
+    binder::Status status = composerServiceAIDL()->isWideColorDisplay(display, supported);
+    return status.transactionError();
 }
 
 status_t Surface::getHdrSupport(bool* supported) {
     ATRACE_CALL();
 
-    const sp<IBinder> display = composerService()->getInternalDisplayToken();
+    const sp<IBinder> display = ComposerServiceAIDL::getInstance().getInternalDisplayToken();
     if (display == nullptr) {
         return NAME_NOT_FOUND;
     }
@@ -1096,6 +1101,17 @@
     *out = input;
 }
 
+void Surface::applyGrallocMetadataLocked(
+        android_native_buffer_t* buffer,
+        const IGraphicBufferProducer::QueueBufferInput& queueBufferInput) {
+    ATRACE_CALL();
+    auto& mapper = GraphicBufferMapper::get();
+    mapper.setDataspace(buffer->handle, static_cast<ui::Dataspace>(queueBufferInput.dataSpace));
+    mapper.setSmpte2086(buffer->handle, queueBufferInput.getHdrMetadata().getSmpte2086());
+    mapper.setCta861_3(buffer->handle, queueBufferInput.getHdrMetadata().getCta8613());
+    mapper.setSmpte2094_40(buffer->handle, queueBufferInput.getHdrMetadata().getHdr10Plus());
+}
+
 void Surface::onBufferQueuedLocked(int slot, sp<Fence> fence,
         const IGraphicBufferProducer::QueueBufferOutput& output) {
     mDequeuedSlots.erase(slot);
@@ -1166,9 +1182,11 @@
     IGraphicBufferProducer::QueueBufferOutput output;
     IGraphicBufferProducer::QueueBufferInput input;
     getQueueBufferInputLocked(buffer, fenceFd, mTimestamp, &input);
+    applyGrallocMetadataLocked(buffer, input);
     sp<Fence> fence = input.fence;
 
     nsecs_t now = systemTime();
+
     status_t err = mGraphicBufferProducer->queueBuffer(i, input, &output);
     mLastQueueDuration = systemTime() - now;
     if (err != OK)  {
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 52a22a7..7a63af0 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -19,6 +19,7 @@
 #include <stdint.h>
 #include <sys/types.h>
 
+#include <android/gui/DisplayState.h>
 #include <android/gui/IWindowInfosListener.h>
 #include <utils/Errors.h>
 #include <utils/Log.h>
@@ -43,6 +44,7 @@
 #include <gui/WindowInfo.h>
 #include <private/gui/ParcelUtils.h>
 #include <ui/DisplayMode.h>
+#include <ui/DisplayState.h>
 #include <ui/DynamicDisplayInfo.h>
 
 #include <private/gui/ComposerService.h>
@@ -206,12 +208,14 @@
 }
 
 sp<TransactionCompletedListener> TransactionCompletedListener::sInstance = nullptr;
+static std::mutex sListenerInstanceMutex;
 
 void TransactionCompletedListener::setInstance(const sp<TransactionCompletedListener>& listener) {
     sInstance = listener;
 }
 
 sp<TransactionCompletedListener> TransactionCompletedListener::getInstance() {
+    std::lock_guard<std::mutex> lock(sListenerInstanceMutex);
     if (sInstance == nullptr) {
         sInstance = new TransactionCompletedListener;
     }
@@ -1022,32 +1026,59 @@
 // ---------------------------------------------------------------------------
 
 sp<IBinder> SurfaceComposerClient::createDisplay(const String8& displayName, bool secure) {
-    return ComposerService::getComposerService()->createDisplay(displayName,
-            secure);
+    sp<IBinder> display = nullptr;
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->createDisplay(std::string(
+                                                                             displayName.string()),
+                                                                     secure, &display);
+    return status.isOk() ? display : nullptr;
 }
 
 void SurfaceComposerClient::destroyDisplay(const sp<IBinder>& display) {
-    return ComposerService::getComposerService()->destroyDisplay(display);
+    ComposerServiceAIDL::getComposerService()->destroyDisplay(display);
 }
 
 std::vector<PhysicalDisplayId> SurfaceComposerClient::getPhysicalDisplayIds() {
-    return ComposerService::getComposerService()->getPhysicalDisplayIds();
+    std::vector<int64_t> displayIds;
+    std::vector<PhysicalDisplayId> physicalDisplayIds;
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->getPhysicalDisplayIds(&displayIds);
+    if (status.isOk()) {
+        physicalDisplayIds.reserve(displayIds.size());
+        for (auto item : displayIds) {
+            auto id = DisplayId::fromValue<PhysicalDisplayId>(static_cast<uint64_t>(item));
+            physicalDisplayIds.push_back(*id);
+        }
+    }
+    return physicalDisplayIds;
 }
 
 status_t SurfaceComposerClient::getPrimaryPhysicalDisplayId(PhysicalDisplayId* id) {
-    return ComposerService::getComposerService()->getPrimaryPhysicalDisplayId(id);
+    int64_t displayId;
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->getPrimaryPhysicalDisplayId(&displayId);
+    if (status.isOk()) {
+        *id = *DisplayId::fromValue<PhysicalDisplayId>(static_cast<uint64_t>(displayId));
+    }
+    return status.transactionError();
 }
 
 std::optional<PhysicalDisplayId> SurfaceComposerClient::getInternalDisplayId() {
-    return ComposerService::getComposerService()->getInternalDisplayId();
+    ComposerServiceAIDL& instance = ComposerServiceAIDL::getInstance();
+    return instance.getInternalDisplayId();
 }
 
 sp<IBinder> SurfaceComposerClient::getPhysicalDisplayToken(PhysicalDisplayId displayId) {
-    return ComposerService::getComposerService()->getPhysicalDisplayToken(displayId);
+    sp<IBinder> display = nullptr;
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->getPhysicalDisplayToken(displayId.value,
+                                                                               &display);
+    return status.isOk() ? display : nullptr;
 }
 
 sp<IBinder> SurfaceComposerClient::getInternalDisplayToken() {
-    return ComposerService::getComposerService()->getInternalDisplayToken();
+    ComposerServiceAIDL& instance = ComposerServiceAIDL::getInstance();
+    return instance.getInternalDisplayToken();
 }
 
 void SurfaceComposerClient::Transaction::setAnimationTransaction() {
@@ -1195,6 +1226,20 @@
     return *this;
 }
 
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setDimmingEnabled(
+        const sp<SurfaceControl>& sc, bool dimmingEnabled) {
+    layer_state_t* s = getLayerState(sc);
+    if (!s) {
+        mStatus = BAD_INDEX;
+        return *this;
+    }
+    s->what |= layer_state_t::eDimmingEnabledChanged;
+    s->dimmingEnabled = dimmingEnabled;
+
+    registerSurfaceControlForCallback(sc);
+    return *this;
+}
+
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setAlpha(
         const sp<SurfaceControl>& sc, float alpha) {
     layer_state_t* s = getLayerState(sc);
@@ -2086,7 +2131,16 @@
 
 status_t SurfaceComposerClient::getDisplayState(const sp<IBinder>& display,
                                                 ui::DisplayState* state) {
-    return ComposerService::getComposerService()->getDisplayState(display, state);
+    gui::DisplayState ds;
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->getDisplayState(display, &ds);
+    if (status.isOk()) {
+        state->layerStack = ui::LayerStack::fromValue(ds.layerStack);
+        state->orientation = static_cast<ui::Rotation>(ds.orientation);
+        state->layerStackSpaceRect =
+                ui::Size(ds.layerStackSpaceRect.width, ds.layerStackSpaceRect.height);
+    }
+    return status.transactionError();
 }
 
 status_t SurfaceComposerClient::getStaticDisplayInfo(const sp<IBinder>& display,
@@ -2150,7 +2204,9 @@
 }
 
 status_t SurfaceComposerClient::getBootDisplayModeSupport(bool* support) {
-    return ComposerService::getComposerService()->getBootDisplayModeSupport(support);
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->getBootDisplayModeSupport(support);
+    return status.transactionError();
 }
 
 status_t SurfaceComposerClient::setBootDisplayMode(const sp<IBinder>& display,
@@ -2159,7 +2215,9 @@
 }
 
 status_t SurfaceComposerClient::clearBootDisplayMode(const sp<IBinder>& display) {
-    return ComposerService::getComposerService()->clearBootDisplayMode(display);
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->clearBootDisplayMode(display);
+    return status.transactionError();
 }
 
 status_t SurfaceComposerClient::setOverrideFrameRate(uid_t uid, float frameRate) {
@@ -2167,16 +2225,16 @@
 }
 
 void SurfaceComposerClient::setAutoLowLatencyMode(const sp<IBinder>& display, bool on) {
-    ComposerService::getComposerService()->setAutoLowLatencyMode(display, on);
+    ComposerServiceAIDL::getComposerService()->setAutoLowLatencyMode(display, on);
 }
 
 void SurfaceComposerClient::setGameContentType(const sp<IBinder>& display, bool on) {
-    ComposerService::getComposerService()->setGameContentType(display, on);
+    ComposerServiceAIDL::getComposerService()->setGameContentType(display, on);
 }
 
 void SurfaceComposerClient::setDisplayPowerMode(const sp<IBinder>& token,
         int mode) {
-    ComposerService::getComposerService()->setPowerMode(token, mode);
+    ComposerServiceAIDL::getComposerService()->setPowerMode(token, mode);
 }
 
 status_t SurfaceComposerClient::getCompositionPreference(
@@ -2237,8 +2295,10 @@
 
 status_t SurfaceComposerClient::isWideColorDisplay(const sp<IBinder>& display,
                                                    bool* outIsWideColorDisplay) {
-    return ComposerService::getComposerService()->isWideColorDisplay(display,
-                                                                     outIsWideColorDisplay);
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->isWideColorDisplay(display,
+                                                                          outIsWideColorDisplay);
+    return status.transactionError();
 }
 
 status_t SurfaceComposerClient::addRegionSamplingListener(
@@ -2275,28 +2335,39 @@
 
 bool SurfaceComposerClient::getDisplayBrightnessSupport(const sp<IBinder>& displayToken) {
     bool support = false;
-    ComposerService::getComposerService()->getDisplayBrightnessSupport(displayToken, &support);
-    return support;
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->getDisplayBrightnessSupport(displayToken,
+                                                                                   &support);
+    return status.isOk() ? support : false;
 }
 
 status_t SurfaceComposerClient::setDisplayBrightness(const sp<IBinder>& displayToken,
                                                      const gui::DisplayBrightness& brightness) {
-    return ComposerService::getComposerService()->setDisplayBrightness(displayToken, brightness);
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->setDisplayBrightness(displayToken,
+                                                                            brightness);
+    return status.transactionError();
 }
 
 status_t SurfaceComposerClient::addHdrLayerInfoListener(
         const sp<IBinder>& displayToken, const sp<gui::IHdrLayerInfoListener>& listener) {
-    return ComposerService::getComposerService()->addHdrLayerInfoListener(displayToken, listener);
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->addHdrLayerInfoListener(displayToken,
+                                                                               listener);
+    return status.transactionError();
 }
 
 status_t SurfaceComposerClient::removeHdrLayerInfoListener(
         const sp<IBinder>& displayToken, const sp<gui::IHdrLayerInfoListener>& listener) {
-    return ComposerService::getComposerService()->removeHdrLayerInfoListener(displayToken,
-                                                                             listener);
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->removeHdrLayerInfoListener(displayToken,
+                                                                                  listener);
+    return status.transactionError();
 }
 
 status_t SurfaceComposerClient::notifyPowerBoost(int32_t boostId) {
-    return ComposerService::getComposerService()->notifyPowerBoost(boostId);
+    binder::Status status = ComposerServiceAIDL::getComposerService()->notifyPowerBoost(boostId);
+    return status.transactionError();
 }
 
 status_t SurfaceComposerClient::setGlobalShadowSettings(const half4& ambientColor,
diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp
index 2312a8c..804ce4f 100644
--- a/libs/gui/WindowInfo.cpp
+++ b/libs/gui/WindowInfo.cpp
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-#include <type_traits>
 #define LOG_TAG "WindowInfo"
 #define LOG_NDEBUG 0
 
+#include <type_traits>
+
 #include <binder/Parcel.h>
 #include <gui/WindowInfo.h>
 
@@ -25,8 +26,7 @@
 
 namespace android::gui {
 
-// --- WindowInfo ---
-void WindowInfo::setInputConfig(Flags<InputConfig> config, bool value) {
+void WindowInfo::setInputConfig(ftl::Flags<InputConfig> config, bool value) {
     if (value) {
         inputConfig |= config;
         return;
@@ -182,18 +182,16 @@
         return status;
     }
 
-    layoutParamsFlags = Flags<Flag>(lpFlags);
+    layoutParamsFlags = ftl::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);
+    inputConfig = ftl::Flags<InputConfig>(inputConfigInt);
     touchableRegionCropHandle = touchableRegionCropHandleSp;
 
     return OK;
 }
 
-// --- WindowInfoHandle ---
-
 WindowInfoHandle::WindowInfoHandle() {}
 
 WindowInfoHandle::~WindowInfoHandle() {}
diff --git a/libs/gui/aidl/android/gui/DisplayStatInfo.aidl b/libs/gui/aidl/android/gui/DisplayStatInfo.aidl
new file mode 100644
index 0000000..68f3942
--- /dev/null
+++ b/libs/gui/aidl/android/gui/DisplayStatInfo.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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 android.gui;
+
+/** @hide */
+parcelable DisplayStatInfo {
+    long vsyncTime;
+    long vsyncPeriod;
+}
diff --git a/libs/gui/aidl/android/gui/DisplayState.aidl b/libs/gui/aidl/android/gui/DisplayState.aidl
new file mode 100644
index 0000000..9589ab6
--- /dev/null
+++ b/libs/gui/aidl/android/gui/DisplayState.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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 android.gui;
+
+import android.gui.Rotation;
+import android.gui.Size;
+
+/** @hide */
+parcelable DisplayState {
+    int layerStack;
+    Rotation orientation = Rotation.Rotation0;
+    Size layerStackSpaceRect;
+}
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
index 07921a5..a9977b0 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -17,11 +17,88 @@
 package android.gui;
 
 import android.gui.DisplayCaptureArgs;
+import android.gui.DisplayBrightness;
+import android.gui.DisplayState;
+import android.gui.DisplayStatInfo;
+import android.gui.IHdrLayerInfoListener;
 import android.gui.LayerCaptureArgs;
 import android.gui.IScreenCaptureListener;
 
 /** @hide */
 interface ISurfaceComposer {
+
+    /* create a virtual display
+     * requires ACCESS_SURFACE_FLINGER permission.
+     */
+    @nullable IBinder createDisplay(@utf8InCpp String displayName, boolean secure);
+
+    /* destroy a virtual display
+     * requires ACCESS_SURFACE_FLINGER permission.
+     */
+    void destroyDisplay(IBinder display);
+
+    /* get stable IDs for connected physical displays.
+     */
+    long[] getPhysicalDisplayIds();
+
+    long getPrimaryPhysicalDisplayId();
+
+    /* get token for a physical display given its stable ID obtained via getPhysicalDisplayIds or a
+     * DisplayEventReceiver hotplug event.
+     */
+    @nullable IBinder getPhysicalDisplayToken(long displayId);
+
+    /* set display power mode. depending on the mode, it can either trigger
+     * screen on, off or low power mode and wait for it to complete.
+     * requires ACCESS_SURFACE_FLINGER permission.
+     */
+    void setPowerMode(IBinder display, int mode);
+
+    /* returns display statistics for a given display
+     * intended to be used by the media framework to properly schedule
+     * video frames */
+    DisplayStatInfo getDisplayStats(IBinder display);
+
+     /**
+     * Get transactional state of given display.
+     */
+    DisplayState getDisplayState(IBinder display);
+
+    /**
+     * Clears the user-preferred display mode. The device should now boot in system preferred
+     * display mode.
+     */
+    void clearBootDisplayMode(IBinder display);
+
+    /**
+     * 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.
+     */
+    // TODO(b/213909104) : Add unit tests to verify surface flinger boot time APIs
+    boolean getBootDisplayModeSupport();
+
+    /**
+     * 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.
+     * For more information, see the HDMI 2.1 specification.
+     */
+    void setAutoLowLatencyMode(IBinder display, boolean on);
+
+    /**
+     * This will start sending infoframes to the connected display with
+     * ContentType=Game (if on=true). This should only be called if the display
+     * Game Content Type as reported in #getDynamicDisplayInfo.
+     * For more information, see the HDMI 1.4 specification.
+     */
+    void setGameContentType(IBinder display, boolean on);
+
     /**
      * Capture the specified screen. This requires READ_FRAME_BUFFER
      * permission.  This function will fail if there is a secure window on
@@ -39,4 +116,67 @@
      * is a secure window on screen
      */
     void captureLayers(in LayerCaptureArgs args, IScreenCaptureListener listener);
+
+    /*
+     * Queries whether the given display is a wide color display.
+     * Requires the ACCESS_SURFACE_FLINGER permission.
+     */
+    boolean isWideColorDisplay(IBinder token);
+
+    /*
+     * Gets whether brightness operations are supported on a display.
+     *
+     * displayToken
+     *      The token of the display.
+     * outSupport
+     *      An output parameter for whether brightness 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.
+     */
+    boolean getDisplayBrightnessSupport(IBinder displayToken);
+
+    /*
+     * Sets the brightness of a display.
+     *
+     * displayToken
+     *      The token of the display whose brightness is set.
+     * brightness
+     *      The DisplayBrightness info to set on the desired display.
+     *
+     * Returns NO_ERROR upon success. Otherwise,
+     *      NAME_NOT_FOUND    if the display is invalid, or
+     *      BAD_VALUE         if the brightness is invalid, or
+     *      INVALID_OPERATION if brightness operations are not supported.
+     */
+    void setDisplayBrightness(IBinder displayToken, in DisplayBrightness brightness);
+
+    /*
+     * Adds a listener that receives HDR layer information. This is used in combination
+     * with setDisplayBrightness to adjust the display brightness depending on factors such
+     * as whether or not HDR is in use.
+     *
+     * Returns NO_ERROR upon success or NAME_NOT_FOUND if the display is invalid.
+     */
+    void addHdrLayerInfoListener(IBinder displayToken, IHdrLayerInfoListener listener);
+
+    /*
+     * Removes a listener that was added with addHdrLayerInfoListener.
+     *
+     * Returns NO_ERROR upon success, NAME_NOT_FOUND if the display is invalid, and BAD_VALUE if
+     *     the listener wasn't registered.
+     *
+     */
+    void removeHdrLayerInfoListener(IBinder displayToken, IHdrLayerInfoListener listener);
+
+    /*
+     * Sends a power boost to the composer. This function is asynchronous.
+     *
+     * boostId
+     *      boost id according to android::hardware::power::Boost
+     *
+     * Returns NO_ERROR upon success.
+     */
+    void notifyPowerBoost(int boostId);
 }
diff --git a/libs/gui/aidl/android/gui/Rect.aidl b/libs/gui/aidl/android/gui/Rect.aidl
new file mode 100644
index 0000000..1b13761
--- /dev/null
+++ b/libs/gui/aidl/android/gui/Rect.aidl
@@ -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 android.gui;
+
+// copied from libs/arect/include/android/rect.h
+// TODO(b/221473398):
+// use hardware/interfaces/graphics/common/aidl/android/hardware/graphics/common/Rect.aidl
+/** @hide */
+parcelable Rect {
+    /// Minimum X coordinate of the rectangle.
+    int left;
+
+    /// Minimum Y coordinate of the rectangle.
+    int top;
+
+    /// Maximum X coordinate of the rectangle.
+    int right;
+
+    /// Maximum Y coordinate of the rectangle.
+    int bottom;
+}
diff --git a/libs/gui/aidl/android/gui/Rotation.aidl b/libs/gui/aidl/android/gui/Rotation.aidl
new file mode 100644
index 0000000..451ff45
--- /dev/null
+++ b/libs/gui/aidl/android/gui/Rotation.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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 android.gui;
+
+/** @hide */
+@Backing(type="int")
+enum Rotation {
+    Rotation0 = 0,
+    Rotation90 = 1,
+    Rotation180 = 2,
+    Rotation270 = 3
+}
diff --git a/libs/gui/aidl/android/gui/Size.aidl b/libs/gui/aidl/android/gui/Size.aidl
new file mode 100644
index 0000000..415fa36
--- /dev/null
+++ b/libs/gui/aidl/android/gui/Size.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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 android.gui;
+
+/** @hide */
+parcelable Size {
+    int width = -1;
+    int height = -1;
+}
diff --git a/libs/gui/include/gui/HdrMetadata.h b/libs/gui/include/gui/HdrMetadata.h
index 0bdffac..98a07a3 100644
--- a/libs/gui/include/gui/HdrMetadata.h
+++ b/libs/gui/include/gui/HdrMetadata.h
@@ -17,6 +17,8 @@
 #pragma once
 
 #include <stdint.h>
+#include <ui/GraphicTypes.h>
+#include <optional>
 #include <vector>
 
 #include <system/graphics.h>
@@ -43,6 +45,27 @@
     status_t flatten(void* buffer, size_t size) const;
     status_t unflatten(void const* buffer, size_t size);
 
+    std::optional<ui::Smpte2086> getSmpte2086() const {
+        if (validTypes & Type::SMPTE2086) {
+            return ui::translate(smpte2086);
+        }
+        return {};
+    }
+
+    std::optional<ui::Cta861_3> getCta8613() const {
+        if (validTypes & Type::CTA861_3) {
+            return ui::translate(cta8613);
+        }
+        return {};
+    }
+
+    std::optional<std::vector<uint8_t>> getHdr10Plus() const {
+        if (validTypes & Type::HDR10PLUS) {
+            return hdr10plus;
+        }
+        return {};
+    }
+
     bool operator==(const HdrMetadata& rhs) const;
     bool operator!=(const HdrMetadata& rhs) const { return !(*this == rhs); }
 };
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index 0a3cc19..a610e94 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -27,7 +27,7 @@
 #include <android/gui/IWindowInfosListener.h>
 #include <binder/IBinder.h>
 #include <binder/IInterface.h>
-#include <ftl/Flags.h>
+#include <ftl/flags.h>
 #include <gui/FrameTimelineInfo.h>
 #include <gui/ITransactionCompletedListener.h>
 #include <gui/SpHash.h>
@@ -126,7 +126,7 @@
         frameRateOverride = 1 << 1,
     };
 
-    using EventRegistrationFlags = Flags<EventRegistration>;
+    using EventRegistrationFlags = ftl::Flags<EventRegistration>;
 
     /*
      * Create a connection with SurfaceFlinger.
@@ -138,40 +138,6 @@
             VsyncSource vsyncSource = eVsyncSourceApp,
             EventRegistrationFlags eventRegistration = {}) = 0;
 
-    /* create a virtual display
-     * requires ACCESS_SURFACE_FLINGER permission.
-     */
-    virtual sp<IBinder> createDisplay(const String8& displayName,
-            bool secure) = 0;
-
-    /* destroy a virtual display
-     * requires ACCESS_SURFACE_FLINGER permission.
-     */
-    virtual void destroyDisplay(const sp<IBinder>& display) = 0;
-
-    /* get stable IDs for connected physical displays.
-     */
-    virtual std::vector<PhysicalDisplayId> getPhysicalDisplayIds() const = 0;
-
-    virtual status_t getPrimaryPhysicalDisplayId(PhysicalDisplayId*) const = 0;
-
-    // TODO(b/74619554): Remove this stopgap once the framework is display-agnostic.
-    std::optional<PhysicalDisplayId> getInternalDisplayId() const {
-        const auto displayIds = getPhysicalDisplayIds();
-        return displayIds.empty() ? std::nullopt : std::make_optional(displayIds.front());
-    }
-
-    /* get token for a physical display given its stable ID obtained via getPhysicalDisplayIds or a
-     * DisplayEventReceiver hotplug event.
-     */
-    virtual sp<IBinder> getPhysicalDisplayToken(PhysicalDisplayId displayId) const = 0;
-
-    // TODO(b/74619554): Remove this stopgap once the framework is display-agnostic.
-    sp<IBinder> getInternalDisplayToken() const {
-        const auto displayId = getInternalDisplayId();
-        return displayId ? getPhysicalDisplayToken(*displayId) : nullptr;
-    }
-
     /* open/close transactions. requires ACCESS_SURFACE_FLINGER permission */
     virtual status_t setTransactionState(
             const FrameTimelineInfo& frameTimelineInfo, const Vector<ComposerState>& state,
@@ -195,24 +161,6 @@
     virtual status_t getSupportedFrameTimestamps(
             std::vector<FrameEvent>* outSupported) const = 0;
 
-    /* set display power mode. depending on the mode, it can either trigger
-     * screen on, off or low power mode and wait for it to complete.
-     * requires ACCESS_SURFACE_FLINGER permission.
-     */
-    virtual void setPowerMode(const sp<IBinder>& display, int mode) = 0;
-
-
-    /* returns display statistics for a given display
-     * intended to be used by the media framework to properly schedule
-     * video frames */
-    virtual status_t getDisplayStats(const sp<IBinder>& display,
-            DisplayStatInfo* stats) = 0;
-
-    /**
-     * Get transactional state of given display.
-     */
-    virtual status_t getDisplayState(const sp<IBinder>& display, ui::DisplayState*) = 0;
-
     /**
      * Gets immutable information about given physical display.
      */
@@ -233,40 +181,6 @@
      */
     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.
-     * For more information, see the HDMI 2.1 specification.
-     */
-    virtual void setAutoLowLatencyMode(const sp<IBinder>& display, bool on) = 0;
-
-    /**
-     * This will start sending infoframes to the connected display with
-     * ContentType=Game (if on=true). This should only be called if the display
-     * Game Content Type as reported in #getDynamicDisplayInfo.
-     * For more information, see the HDMI 1.4 specification.
-     */
-    virtual void setGameContentType(const sp<IBinder>& display, bool on) = 0;
-
     /* Clears the frame statistics for animations.
      *
      * Requires the ACCESS_SURFACE_FLINGER permission.
@@ -345,13 +259,6 @@
      */
     virtual status_t getProtectedContentSupport(bool* outSupported) const = 0;
 
-    /*
-     * Queries whether the given display is a wide color display.
-     * Requires the ACCESS_SURFACE_FLINGER permission.
-     */
-    virtual status_t isWideColorDisplay(const sp<IBinder>& token,
-                                        bool* outIsWideColorDisplay) const = 0;
-
     /* Registers a listener to stream median luma updates from SurfaceFlinger.
      *
      * The sampling area is bounded by both samplingArea and the given stopLayerHandle
@@ -432,65 +339,6 @@
                                                 float* outPrimaryRefreshRateMax,
                                                 float* outAppRequestRefreshRateMin,
                                                 float* outAppRequestRefreshRateMax) = 0;
-    /*
-     * Gets whether brightness operations are supported on a display.
-     *
-     * displayToken
-     *      The token of the display.
-     * outSupport
-     *      An output parameter for whether brightness 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 getDisplayBrightnessSupport(const sp<IBinder>& displayToken,
-                                                 bool* outSupport) const = 0;
-
-    /*
-     * Sets the brightness of a display.
-     *
-     * displayToken
-     *      The token of the display whose brightness is set.
-     * brightness
-     *      The DisplayBrightness info to set on the desired display.
-     *
-     * Returns NO_ERROR upon success. Otherwise,
-     *      NAME_NOT_FOUND    if the display is invalid, or
-     *      BAD_VALUE         if the brightness is invalid, or
-     *      INVALID_OPERATION if brightness operations are not supported.
-     */
-    virtual status_t setDisplayBrightness(const sp<IBinder>& displayToken,
-                                          const gui::DisplayBrightness& brightness) = 0;
-
-    /*
-     * Adds a listener that receives HDR layer information. This is used in combination
-     * with setDisplayBrightness to adjust the display brightness depending on factors such
-     * as whether or not HDR is in use.
-     *
-     * Returns NO_ERROR upon success or NAME_NOT_FOUND if the display is invalid.
-     */
-    virtual status_t addHdrLayerInfoListener(const sp<IBinder>& displayToken,
-                                             const sp<gui::IHdrLayerInfoListener>& listener) = 0;
-    /*
-     * Removes a listener that was added with addHdrLayerInfoListener.
-     *
-     * Returns NO_ERROR upon success, NAME_NOT_FOUND if the display is invalid, and BAD_VALUE if
-     *     the listener wasn't registered.
-     *
-     */
-    virtual status_t removeHdrLayerInfoListener(const sp<IBinder>& displayToken,
-                                                const sp<gui::IHdrLayerInfoListener>& listener) = 0;
-
-    /*
-     * Sends a power boost to the composer. This function is asynchronous.
-     *
-     * boostId
-     *      boost id according to android::hardware::power::Boost
-     *
-     * Returns NO_ERROR upon success.
-     */
-    virtual status_t notifyPowerBoost(int32_t boostId) = 0;
 
     /*
      * Sets the global configuration for all the shadows drawn by SurfaceFlinger. Shadow follows
@@ -597,9 +445,9 @@
         CREATE_CONNECTION,
         GET_STATIC_DISPLAY_INFO,
         CREATE_DISPLAY_EVENT_CONNECTION,
-        CREATE_DISPLAY,
-        DESTROY_DISPLAY,
-        GET_PHYSICAL_DISPLAY_TOKEN,
+        CREATE_DISPLAY,             // Deprecated. Autogenerated by .aidl now.
+        DESTROY_DISPLAY,            // Deprecated. Autogenerated by .aidl now.
+        GET_PHYSICAL_DISPLAY_TOKEN, // Deprecated. Autogenerated by .aidl now.
         SET_TRANSACTION_STATE,
         AUTHENTICATE_SURFACE,
         GET_SUPPORTED_FRAME_TIMESTAMPS,
@@ -610,7 +458,7 @@
         CAPTURE_LAYERS,  // Deprecated. Autogenerated by .aidl now.
         CLEAR_ANIMATION_FRAME_STATS,
         GET_ANIMATION_FRAME_STATS,
-        SET_POWER_MODE,
+        SET_POWER_MODE, // Deprecated. Autogenerated by .aidl now.
         GET_DISPLAY_STATS,
         GET_HDR_CAPABILITIES,    // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead.
         GET_DISPLAY_COLOR_MODES, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead.
@@ -625,22 +473,22 @@
         SET_DISPLAY_CONTENT_SAMPLING_ENABLED,
         GET_DISPLAYED_CONTENT_SAMPLE,
         GET_PROTECTED_CONTENT_SUPPORT,
-        IS_WIDE_COLOR_DISPLAY,
+        IS_WIDE_COLOR_DISPLAY, // Deprecated. Autogenerated by .aidl now.
         GET_DISPLAY_NATIVE_PRIMARIES,
-        GET_PHYSICAL_DISPLAY_IDS,
+        GET_PHYSICAL_DISPLAY_IDS, // Deprecated. Autogenerated by .aidl now.
         ADD_REGION_SAMPLING_LISTENER,
         REMOVE_REGION_SAMPLING_LISTENER,
         SET_DESIRED_DISPLAY_MODE_SPECS,
         GET_DESIRED_DISPLAY_MODE_SPECS,
-        GET_DISPLAY_BRIGHTNESS_SUPPORT,
-        SET_DISPLAY_BRIGHTNESS,
-        CAPTURE_DISPLAY_BY_ID, // Deprecated. Autogenerated by .aidl now.
-        NOTIFY_POWER_BOOST,
+        GET_DISPLAY_BRIGHTNESS_SUPPORT, // Deprecated. Autogenerated by .aidl now.
+        SET_DISPLAY_BRIGHTNESS,         // Deprecated. Autogenerated by .aidl now.
+        CAPTURE_DISPLAY_BY_ID,          // Deprecated. Autogenerated by .aidl now.
+        NOTIFY_POWER_BOOST,             // Deprecated. Autogenerated by .aidl now.
         SET_GLOBAL_SHADOW_SETTINGS,
         GET_AUTO_LOW_LATENCY_MODE_SUPPORT, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead.
-        SET_AUTO_LOW_LATENCY_MODE,
-        GET_GAME_CONTENT_TYPE_SUPPORT, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead.
-        SET_GAME_CONTENT_TYPE,
+        SET_AUTO_LOW_LATENCY_MODE,         // Deprecated. Autogenerated by .aidl now.
+        GET_GAME_CONTENT_TYPE_SUPPORT,     // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead.
+        SET_GAME_CONTENT_TYPE,             // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead.
         SET_FRAME_RATE,
         // Deprecated. Use DisplayManager.setShouldAlwaysRespectAppRequestedMode(true);
         ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN,
@@ -652,18 +500,18 @@
         ADD_FPS_LISTENER,
         REMOVE_FPS_LISTENER,
         OVERRIDE_HDR_TYPES,
-        ADD_HDR_LAYER_INFO_LISTENER,
-        REMOVE_HDR_LAYER_INFO_LISTENER,
+        ADD_HDR_LAYER_INFO_LISTENER,    // Deprecated. Autogenerated by .aidl now.
+        REMOVE_HDR_LAYER_INFO_LISTENER, // Deprecated. Autogenerated by .aidl now.
         ON_PULL_ATOM,
         ADD_TUNNEL_MODE_ENABLED_LISTENER,
         REMOVE_TUNNEL_MODE_ENABLED_LISTENER,
         ADD_WINDOW_INFOS_LISTENER,
         REMOVE_WINDOW_INFOS_LISTENER,
-        GET_PRIMARY_PHYSICAL_DISPLAY_ID,
+        GET_PRIMARY_PHYSICAL_DISPLAY_ID, // Deprecated. Autogenerated by .aidl now.
         GET_DISPLAY_DECORATION_SUPPORT,
-        GET_BOOT_DISPLAY_MODE_SUPPORT,
+        GET_BOOT_DISPLAY_MODE_SUPPORT, // Deprecated. Autogenerated by .aidl now.
         SET_BOOT_DISPLAY_MODE,
-        CLEAR_BOOT_DISPLAY_MODE,
+        CLEAR_BOOT_DISPLAY_MODE, // Deprecated. Autogenerated by .aidl now.
         SET_OVERRIDE_FRAME_RATE,
         // Always append new enum to the end.
     };
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 0f37dab..0a9b75a 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -29,6 +29,7 @@
 #include <android/gui/DropInputMode.h>
 #include <android/gui/FocusRequest.h>
 
+#include <ftl/flags.h>
 #include <gui/DisplayCaptureArgs.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/LayerCaptureArgs.h>
@@ -102,7 +103,7 @@
     // was called with.
     sp<IBinder> releaseBufferEndpoint;
 
-    Flags<BufferDataChange> flags;
+    ftl::Flags<BufferDataChange> flags;
 
     client_cache_t cachedBuffer;
 
@@ -150,7 +151,7 @@
         eTransparentRegionChanged = 0x00000020,
         eFlagsChanged = 0x00000040,
         eLayerStackChanged = 0x00000080,
-        /* unused 0x00000400, */
+        eDimmingEnabledChanged = 0x00000400,
         eShadowRadiusChanged = 0x00000800,
         /* unused 0x00001000, */
         eBufferCropChanged = 0x00002000,
@@ -187,7 +188,7 @@
         eAutoRefreshChanged = 0x1000'00000000,
         eStretchChanged = 0x2000'00000000,
         eTrustedOverlayChanged = 0x4000'00000000,
-        eDropInputModeChanged = 0x8000'00000000,
+        eDropInputModeChanged = 0x8000'00000000
     };
 
     layer_state_t();
@@ -298,6 +299,8 @@
 
     // Force inputflinger to drop all input events for the layer and its children.
     gui::DropInputMode dropInputMode;
+
+    bool dimmingEnabled;
 };
 
 struct ComposerState {
diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h
index 40d096e..ab9ebaa 100644
--- a/libs/gui/include/gui/Surface.h
+++ b/libs/gui/include/gui/Surface.h
@@ -35,6 +35,10 @@
 
 namespace android {
 
+namespace gui {
+class ISurfaceComposer;
+} // namespace gui
+
 class ISurfaceComposer;
 
 /* This is the same as ProducerListener except that onBuffersDiscarded is
@@ -196,6 +200,7 @@
 
     // Virtual for testing.
     virtual sp<ISurfaceComposer> composerService() const;
+    virtual sp<gui::ISurfaceComposer> composerServiceAIDL() const;
     virtual nsecs_t now() const;
 
 private:
@@ -398,6 +403,13 @@
     void getQueueBufferInputLocked(android_native_buffer_t* buffer, int fenceFd, nsecs_t timestamp,
             IGraphicBufferProducer::QueueBufferInput* out);
 
+    // For easing in adoption of gralloc4 metadata by vendor components, as well as for supporting
+    // the public ANativeWindow api, allow setting relevant metadata when queueing a buffer through
+    // a native window
+    void applyGrallocMetadataLocked(
+            android_native_buffer_t* buffer,
+            const IGraphicBufferProducer::QueueBufferInput& queueBufferInput);
+
     void onBufferQueuedLocked(int slot, sp<Fence> fence,
             const IGraphicBufferProducer::QueueBufferOutput& output);
 
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 9d03f58..0cc43d8 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -491,6 +491,7 @@
                 uint32_t flags, uint32_t mask);
         Transaction& setTransparentRegionHint(const sp<SurfaceControl>& sc,
                 const Region& transparentRegion);
+        Transaction& setDimmingEnabled(const sp<SurfaceControl>& sc, bool dimmingEnabled);
         Transaction& setAlpha(const sp<SurfaceControl>& sc,
                 float alpha);
         Transaction& setMatrix(const sp<SurfaceControl>& sc,
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index ef0b98b..0e1d258 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -20,7 +20,7 @@
 #include <android/os/InputConfig.h>
 #include <binder/Parcel.h>
 #include <binder/Parcelable.h>
-#include <ftl/Flags.h>
+#include <ftl/flags.h>
 #include <gui/constants.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
@@ -224,7 +224,7 @@
     int32_t ownerPid = -1;
     int32_t ownerUid = -1;
     std::string packageName;
-    Flags<InputConfig> inputConfig;
+    ftl::Flags<InputConfig> inputConfig;
     int32_t displayId = ADISPLAY_ID_NONE;
     InputApplicationInfo applicationInfo;
     bool replaceTouchableRegionWithCrop = false;
@@ -232,9 +232,9 @@
 
     // The window's layout params flags and type set by WM.
     Type layoutParamsType = Type::UNKNOWN;
-    Flags<Flag> layoutParamsFlags;
+    ftl::Flags<Flag> layoutParamsFlags;
 
-    void setInputConfig(Flags<InputConfig> config, bool value);
+    void setInputConfig(ftl::Flags<InputConfig> config, bool value);
 
     void addTouchableRegion(const Rect& region);
 
diff --git a/libs/gui/include/private/gui/ComposerServiceAIDL.h b/libs/gui/include/private/gui/ComposerServiceAIDL.h
index fee37ee..9a96976 100644
--- a/libs/gui/include/private/gui/ComposerServiceAIDL.h
+++ b/libs/gui/include/private/gui/ComposerServiceAIDL.h
@@ -50,6 +50,28 @@
     // 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();
+
+    // the following two methods are moved from ISurfaceComposer.h
+    // TODO(b/74619554): Remove this stopgap once the framework is display-agnostic.
+    std::optional<PhysicalDisplayId> getInternalDisplayId() const {
+        std::vector<int64_t> displayIds;
+        binder::Status status = mComposerService->getPhysicalDisplayIds(&displayIds);
+        return (!status.isOk() || displayIds.empty())
+                ? std::nullopt
+                : DisplayId::fromValue<PhysicalDisplayId>(
+                          static_cast<uint64_t>(displayIds.front()));
+    }
+
+    // TODO(b/74619554): Remove this stopgap once the framework is display-agnostic.
+    sp<IBinder> getInternalDisplayToken() const {
+        const auto displayId = getInternalDisplayId();
+        if (!displayId) return nullptr;
+        sp<IBinder> display;
+        binder::Status status =
+                mComposerService->getPhysicalDisplayToken(static_cast<int64_t>(displayId->value),
+                                                          &display);
+        return status.isOk() ? display : nullptr;
+    }
 };
 
 // ---------------------------------------------------------------------------
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index a885e92..065cd7a 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -20,6 +20,7 @@
 
 #include <SurfaceFlingerProperties.h>
 #include <android/gui/IDisplayEventConnection.h>
+#include <android/gui/ISurfaceComposer.h>
 #include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
 #include <binder/ProcessState.h>
 #include <configstore/Utils.h>
@@ -260,9 +261,7 @@
     sp<ANativeWindow> anw(mSurface);
 
     // Verify the screenshot works with no protected buffers.
-    sp<ISurfaceComposer> sf(ComposerService::getComposerService());
-
-    const sp<IBinder> display = sf->getInternalDisplayToken();
+    const sp<IBinder> display = ComposerServiceAIDL::getInstance().getInternalDisplayToken();
     ASSERT_FALSE(display == nullptr);
 
     DisplayCaptureArgs captureArgs;
@@ -696,12 +695,6 @@
             ISurfaceComposer::VsyncSource, ISurfaceComposer::EventRegistrationFlags) override {
         return nullptr;
     }
-    sp<IBinder> createDisplay(const String8& /*displayName*/,
-            bool /*secure*/) override { return nullptr; }
-    void destroyDisplay(const sp<IBinder>& /*display */) override {}
-    std::vector<PhysicalDisplayId> getPhysicalDisplayIds() const override { return {}; }
-    status_t getPrimaryPhysicalDisplayId(PhysicalDisplayId*) const override { return NO_ERROR; }
-    sp<IBinder> getPhysicalDisplayToken(PhysicalDisplayId) const override { return nullptr; }
     status_t setTransactionState(const FrameTimelineInfo& /*frameTimelineInfo*/,
                                  const Vector<ComposerState>& /*state*/,
                                  const Vector<DisplayState>& /*displays*/, uint32_t /*flags*/,
@@ -740,7 +733,6 @@
         return NO_ERROR;
     }
 
-    void setPowerMode(const sp<IBinder>& /*display*/, int /*mode*/) override {}
     status_t getStaticDisplayInfo(const sp<IBinder>& /*display*/, ui::StaticDisplayInfo*) override {
         return NO_ERROR;
     }
@@ -748,24 +740,16 @@
                                    ui::DynamicDisplayInfo*) override {
         return NO_ERROR;
     }
-    status_t getDisplayState(const sp<IBinder>& /*display*/, ui::DisplayState*) override {
-        return NO_ERROR;
-    }
-    status_t getDisplayStats(const sp<IBinder>& /*display*/,
-            DisplayStatInfo* /*stats*/) override { return NO_ERROR; }
     status_t getDisplayNativePrimaries(const sp<IBinder>& /*display*/,
             ui::DisplayPrimaries& /*primaries*/) override {
         return NO_ERROR;
     }
-    status_t setActiveColorMode(const sp<IBinder>& /*display*/,
-        ColorMode /*colorMode*/) override { return NO_ERROR; }
-    status_t getBootDisplayModeSupport(bool* /*outSupport*/) const override { return NO_ERROR; }
+    status_t setActiveColorMode(const sp<IBinder>& /*display*/, ColorMode /*colorMode*/) 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 clearAnimationFrameStats() override { return NO_ERROR; }
     status_t getAnimationFrameStats(FrameStats* /*outStats*/) const override {
@@ -812,26 +796,6 @@
     status_t getColorManagement(bool* /*outGetColorManagement*/) const override { return NO_ERROR; }
     status_t getProtectedContentSupport(bool* /*outSupported*/) const override { return NO_ERROR; }
 
-    status_t isWideColorDisplay(const sp<IBinder>&, bool*) const override { return NO_ERROR; }
-    status_t getDisplayBrightnessSupport(const sp<IBinder>& /*displayToken*/,
-                                         bool* /*outSupport*/) const override {
-        return NO_ERROR;
-    }
-    status_t setDisplayBrightness(const sp<IBinder>& /*displayToken*/,
-                                  const gui::DisplayBrightness& /*brightness*/) override {
-        return NO_ERROR;
-    }
-
-    status_t addHdrLayerInfoListener(const sp<IBinder>&,
-                                     const sp<gui::IHdrLayerInfoListener>&) override {
-        return NO_ERROR;
-    }
-
-    status_t removeHdrLayerInfoListener(const sp<IBinder>&,
-                                        const sp<gui::IHdrLayerInfoListener>&) override {
-        return NO_ERROR;
-    }
-
     status_t addRegionSamplingListener(const Rect& /*samplingArea*/,
                                        const sp<IBinder>& /*stopLayerHandle*/,
                                        const sp<IRegionSamplingListener>& /*listener*/) override {
@@ -873,7 +837,6 @@
                                         float* /*outAppRequestRefreshRateMax*/) override {
         return NO_ERROR;
     };
-    status_t notifyPowerBoost(int32_t /*boostId*/) override { return NO_ERROR; }
 
     status_t setGlobalShadowSettings(const half4& /*ambientColor*/, const half4& /*spotColor*/,
                                      float /*lightPosY*/, float /*lightPosZ*/,
@@ -925,6 +888,114 @@
     bool mSupportsPresent{true};
 };
 
+class FakeSurfaceComposerAIDL : public gui::ISurfaceComposer {
+public:
+    ~FakeSurfaceComposerAIDL() override {}
+
+    void setSupportsPresent(bool supportsPresent) { mSupportsPresent = supportsPresent; }
+
+    binder::Status createDisplay(const std::string& /*displayName*/, bool /*secure*/,
+                                 sp<IBinder>* /*outDisplay*/) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status destroyDisplay(const sp<IBinder>& /*display*/) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status getPhysicalDisplayIds(std::vector<int64_t>* /*outDisplayIds*/) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status getPrimaryPhysicalDisplayId(int64_t* /*outDisplayId*/) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status getPhysicalDisplayToken(int64_t /*displayId*/,
+                                           sp<IBinder>* /*outDisplay*/) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status setPowerMode(const sp<IBinder>& /*display*/, int /*mode*/) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status getDisplayStats(const sp<IBinder>& /*display*/,
+                                   gui::DisplayStatInfo* /*outStatInfo*/) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status getDisplayState(const sp<IBinder>& /*display*/,
+                                   gui::DisplayState* /*outState*/) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status clearBootDisplayMode(const sp<IBinder>& /*display*/) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status getBootDisplayModeSupport(bool* /*outMode*/) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status setAutoLowLatencyMode(const sp<IBinder>& /*display*/, bool /*on*/) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status setGameContentType(const sp<IBinder>& /*display*/, bool /*on*/) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status captureDisplay(const DisplayCaptureArgs&,
+                                  const sp<IScreenCaptureListener>&) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status captureDisplayById(int64_t, const sp<IScreenCaptureListener>&) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status captureLayers(const LayerCaptureArgs&,
+                                 const sp<IScreenCaptureListener>&) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status isWideColorDisplay(const sp<IBinder>& /*token*/,
+                                      bool* /*outIsWideColorDisplay*/) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status getDisplayBrightnessSupport(const sp<IBinder>& /*displayToken*/,
+                                               bool* /*outSupport*/) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status setDisplayBrightness(const sp<IBinder>& /*displayToken*/,
+                                        const gui::DisplayBrightness& /*brightness*/) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status addHdrLayerInfoListener(
+            const sp<IBinder>& /*displayToken*/,
+            const sp<gui::IHdrLayerInfoListener>& /*listener*/) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status removeHdrLayerInfoListener(
+            const sp<IBinder>& /*displayToken*/,
+            const sp<gui::IHdrLayerInfoListener>& /*listener*/) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status notifyPowerBoost(int /*boostId*/) override { return binder::Status::ok(); }
+
+protected:
+    IBinder* onAsBinder() override { return nullptr; }
+
+private:
+    bool mSupportsPresent{true};
+};
+
 class FakeProducerFrameEventHistory : public ProducerFrameEventHistory {
 public:
     explicit FakeProducerFrameEventHistory(FenceToFenceTimeMap* fenceMap) : mFenceMap(fenceMap) {}
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 18fb7c1..5d7874a 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -50,6 +50,7 @@
         "Keyboard.cpp",
         "KeyCharacterMap.cpp",
         "KeyLayoutMap.cpp",
+        "PrintTools.cpp",
         "PropertyMap.cpp",
         "TouchVideoFrame.cpp",
         "VelocityControl.cpp",
diff --git a/libs/input/PrintTools.cpp b/libs/input/PrintTools.cpp
new file mode 100644
index 0000000..5d6ae4e
--- /dev/null
+++ b/libs/input/PrintTools.cpp
@@ -0,0 +1,27 @@
+/*
+ * 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 "PrintTools"
+
+#include <input/PrintTools.h>
+
+namespace android {
+
+const char* toString(bool value) {
+    return value ? "true" : "false";
+}
+
+} // namespace android
diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp
index a6465ee..7f427f2 100644
--- a/libs/input/VelocityTracker.cpp
+++ b/libs/input/VelocityTracker.cpp
@@ -15,13 +15,6 @@
  */
 
 #define LOG_TAG "VelocityTracker"
-//#define LOG_NDEBUG 0
-
-// Log debug messages about velocity tracking.
-static constexpr bool DEBUG_VELOCITY = false;
-
-// Log debug messages about the progress of the algorithm itself.
-static constexpr bool DEBUG_STRATEGY = false;
 
 #include <array>
 #include <inttypes.h>
@@ -36,6 +29,27 @@
 
 namespace android {
 
+/**
+ * Log debug messages about velocity tracking.
+ * Enable this via "adb shell setprop log.tag.VelocityTrackerVelocity DEBUG" (requires restart)
+ */
+const bool DEBUG_VELOCITY =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Velocity", ANDROID_LOG_INFO);
+
+/**
+ * Log debug messages about the progress of the algorithm itself.
+ * Enable this via "adb shell setprop log.tag.VelocityTrackerStrategy DEBUG" (requires restart)
+ */
+const bool DEBUG_STRATEGY =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Strategy", ANDROID_LOG_INFO);
+
+/**
+ * Log debug messages about the 'impulse' strategy.
+ * Enable this via "adb shell setprop log.tag.VelocityTrackerImpulse DEBUG" (requires restart)
+ */
+const bool DEBUG_IMPULSE =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Impulse", ANDROID_LOG_INFO);
+
 // Nanoseconds per milliseconds.
 static const nsecs_t NANOS_PER_MS = 1000000;
 
@@ -141,7 +155,7 @@
             return std::make_unique<LeastSquaresVelocityTrackerStrategy>(1);
 
         case VelocityTracker::Strategy::LSQ2:
-            if (DEBUG_STRATEGY) {
+            if (DEBUG_STRATEGY && !DEBUG_IMPULSE) {
                 ALOGI("Initializing lsq2 strategy");
             }
             return std::make_unique<LeastSquaresVelocityTrackerStrategy>(2);
@@ -1172,7 +1186,25 @@
     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]);
+        ALOGD("velocity: (%.1f, %.1f)", outEstimator->xCoeff[1], outEstimator->yCoeff[1]);
+    }
+    if (DEBUG_IMPULSE) {
+        // TODO(b/134179997): delete this block once the switch to 'impulse' is complete.
+        // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons
+        VelocityTracker lsq2(VelocityTracker::Strategy::LSQ2);
+        BitSet32 idBits;
+        const uint32_t pointerId = 0;
+        idBits.markBit(pointerId);
+        for (ssize_t i = m - 1; i >= 0; i--) {
+            lsq2.addMovement(time[i], idBits, {{x[i], y[i]}});
+        }
+        float outVx = 0, outVy = 0;
+        const bool computed = lsq2.getVelocity(pointerId, &outVx, &outVy);
+        if (computed) {
+            ALOGD("lsq2 velocity: (%.1f, %.1f)", outVx, outVy);
+        } else {
+            ALOGD("lsq2 velocity: could not compute velocity");
+        }
     }
     return true;
 }
diff --git a/libs/nativewindow/AHardwareBuffer.cpp b/libs/nativewindow/AHardwareBuffer.cpp
index 381900e..4a1784e 100644
--- a/libs/nativewindow/AHardwareBuffer.cpp
+++ b/libs/nativewindow/AHardwareBuffer.cpp
@@ -207,7 +207,11 @@
       if (result == 0) {
         outPlanes->planeCount = 3;
         outPlanes->planes[0].data = yuvData.y;
-        outPlanes->planes[0].pixelStride = 1;
+        if (format == AHARDWAREBUFFER_FORMAT_YCbCr_P010) {
+            outPlanes->planes[0].pixelStride = 2;
+        } else {
+            outPlanes->planes[0].pixelStride = 1;
+        }
         outPlanes->planes[0].rowStride = yuvData.ystride;
         outPlanes->planes[1].data = yuvData.cb;
         outPlanes->planes[1].pixelStride = yuvData.chroma_step;
diff --git a/libs/nativewindow/include/apex/window.h b/libs/nativewindow/include/apex/window.h
index 0923438..2d1354c 100644
--- a/libs/nativewindow/include/apex/window.h
+++ b/libs/nativewindow/include/apex/window.h
@@ -39,19 +39,6 @@
     // clang-format on
 };
 
-/*
- * Internal extension of compatibility value for ANativeWindow_setFrameRate. */
-enum ANativeWindow_FrameRateCompatibilityInternal {
-    /**
-     * This surface belongs to an app on the High Refresh Rate Deny list, and needs the display
-     * to operate at the exact frame rate.
-     *
-     * This is used internally by the platform and should not be used by apps.
-     * @hide
-     */
-    ANATIVEWINDOW_FRAME_RATE_EXACT = 100,
-};
-
 /**
  * Prototype of the function that an ANativeWindow implementation would call
  * when ANativeWindow_cancelBuffer is called.
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index a319769..a54af1f 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -1018,6 +1018,24 @@
     return window->perform(window, NATIVE_WINDOW_SET_AUTO_PREROTATION, autoPrerotation);
 }
 
+/*
+ * Internal extension of ANativeWindow_FrameRateCompatibility.
+ */
+enum {
+    /**
+     * This surface belongs to an app on the High Refresh Rate Deny list, and needs the display
+     * to operate at the exact frame rate.
+     *
+     * Keep in sync with Surface.java constant.
+     */
+    ANATIVEWINDOW_FRAME_RATE_EXACT = 100,
+
+    /**
+     * This surface is ignored while choosing the refresh rate.
+     */
+    ANATIVEWINDOW_FRAME_RATE_NO_VOTE,
+};
+
 static inline int native_window_set_frame_rate(struct ANativeWindow* window, float frameRate,
                                         int8_t compatibility, int8_t changeFrameRateStrategy) {
     return window->perform(window, NATIVE_WINDOW_SET_FRAME_RATE, (double)frameRate,
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index 84e84dd..cb92df3 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -27,6 +27,7 @@
         "-DEGL_EGLEXT_PROTOTYPES",
     ],
     shared_libs: [
+        "android.hardware.graphics.composer3-V1-ndk",
         "libbase",
         "libcutils",
         "libEGL",
diff --git a/libs/renderengine/benchmark/Android.bp b/libs/renderengine/benchmark/Android.bp
index 471159f..249fec5 100644
--- a/libs/renderengine/benchmark/Android.bp
+++ b/libs/renderengine/benchmark/Android.bp
@@ -43,6 +43,7 @@
     ],
 
     shared_libs: [
+        "android.hardware.graphics.composer3-V1-ndk",
         "libbase",
         "libcutils",
         "libjnigraphics",
diff --git a/libs/renderengine/include/renderengine/DisplaySettings.h b/libs/renderengine/include/renderengine/DisplaySettings.h
index 40ba5ad..a5e0879 100644
--- a/libs/renderengine/include/renderengine/DisplaySettings.h
+++ b/libs/renderengine/include/renderengine/DisplaySettings.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include <aidl/android/hardware/graphics/composer3/DimmingStage.h>
+#include <aidl/android/hardware/graphics/composer3/RenderIntent.h>
 #include <iosfwd>
 
 #include <math/mat4.h>
@@ -68,6 +70,14 @@
     // 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;
+
+    // Configures when dimming should be applied for each layer.
+    aidl::android::hardware::graphics::composer3::DimmingStage dimmingStage =
+            aidl::android::hardware::graphics::composer3::DimmingStage::NONE;
+
+    // Configures the rendering intent of the output display. This is used for tonemapping.
+    aidl::android::hardware::graphics::composer3::RenderIntent renderIntent =
+            aidl::android::hardware::graphics::composer3::RenderIntent::TONE_MAP_COLORIMETRIC;
 };
 
 static inline bool operator==(const DisplaySettings& lhs, const DisplaySettings& rhs) {
diff --git a/libs/renderengine/skia/ColorSpaces.cpp b/libs/renderengine/skia/ColorSpaces.cpp
index f367a84..37ff5df 100644
--- a/libs/renderengine/skia/ColorSpaces.cpp
+++ b/libs/renderengine/skia/ColorSpaces.cpp
@@ -65,7 +65,15 @@
         case HAL_DATASPACE_TRANSFER_SMPTE_170M:
             return SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, gamut);
         case HAL_DATASPACE_TRANSFER_HLG:
-            return SkColorSpace::MakeRGB(SkNamedTransferFn::kHLG, gamut);
+            // return HLG transfer but scale by 1/12
+            skcms_TransferFunction hlgFn;
+            if (skcms_TransferFunction_makeScaledHLGish(&hlgFn, 1.f / 12.f, 2.f, 2.f,
+                                                        1.f / 0.17883277f, 0.28466892f,
+                                                        0.55991073f)) {
+                return SkColorSpace::MakeRGB(hlgFn, gamut);
+            } else {
+                return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, 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 1665550..9bb68e3 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -39,6 +39,7 @@
 #include <gui/TraceUtils.h>
 #include <sync/sync.h>
 #include <ui/BlurRegion.h>
+#include <ui/DataspaceUtils.h>
 #include <ui/DebugUtils.h>
 #include <ui/GraphicBuffer.h>
 #include <utils/Trace.h>
@@ -291,6 +292,8 @@
 void SkiaGLRenderEngine::SkSLCacheMonitor::store(const SkData& key, const SkData& data,
                                                  const SkString& description) {
     mShadersCachedSinceLastCall++;
+    mTotalShadersCompiled++;
+    ATRACE_FORMAT("SF cache: %i shaders", mTotalShadersCompiled);
 }
 
 void SkiaGLRenderEngine::assertShadersCompiled(int numShaders) {
@@ -661,7 +664,7 @@
                                         parameters.display.maxLuminance,
                                         parameters.display.currentLuminanceNits,
                                         parameters.layer.source.buffer.maxLuminanceNits,
-                                        hardwareBuffer);
+                                        hardwareBuffer, parameters.display.renderIntent);
     }
     return parameters.shader;
 }
@@ -736,7 +739,9 @@
     return roundedRect;
 }
 
-static bool equalsWithinMargin(float expected, float value, float margin) {
+// Arbitrary default margin which should be close enough to zero.
+constexpr float kDefaultMargin = 0.0001f;
+static bool equalsWithinMargin(float expected, float value, float margin = kDefaultMargin) {
     LOG_ALWAYS_FATAL_IF(margin < 0.f, "Margin is negative!");
     return std::abs(expected - value) < margin;
 }
@@ -994,10 +999,13 @@
                 ? displayDimmingRatio
                 : (layer.whitePointNits / maxLayerWhitePoint) * displayDimmingRatio;
 
+        const bool dimInLinearSpace = display.dimmingStage !=
+                aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF;
+
         const bool requiresLinearEffect = layer.colorTransform != mat4() ||
                 (mUseColorManagement &&
                  needsToneMapping(layer.sourceDataspace, display.outputDataspace)) ||
-                !equalsWithinMargin(1.f, layerDimmingRatio, 0.001f);
+                (dimInLinearSpace && !equalsWithinMargin(1.f, layerDimmingRatio));
 
         // quick abort from drawing the remaining portion of the layer
         if (layer.skipContentDraw ||
@@ -1103,11 +1111,19 @@
                                                   .undoPremultipliedAlpha = !item.isOpaque &&
                                                           item.usePremultipliedAlpha,
                                                   .requiresLinearEffect = requiresLinearEffect,
-                                                  .layerDimmingRatio = layerDimmingRatio}));
+                                                  .layerDimmingRatio = dimInLinearSpace
+                                                          ? layerDimmingRatio
+                                                          : 1.f}));
 
-            // Turn on dithering when dimming beyond this threshold.
+            // Turn on dithering when dimming beyond this (arbitrary) threshold...
             static constexpr float kDimmingThreshold = 0.2f;
-            if (layerDimmingRatio <= kDimmingThreshold) {
+            // ...or we're rendering an HDR layer down to an 8-bit target
+            // Most HDR standards require at least 10-bits of color depth for source content, so we
+            // can just extract the transfer function rather than dig into precise gralloc layout.
+            // Furthermore, we can assume that the only 8-bit target we support is RGBA8888.
+            const bool requiresDownsample = isHdrDataspace(layer.sourceDataspace) &&
+                    buffer->getPixelFormat() == PIXEL_FORMAT_RGBA_8888;
+            if (layerDimmingRatio <= kDimmingThreshold || requiresDownsample) {
                 paint.setDither(true);
             }
             paint.setAlphaf(layer.alpha);
@@ -1170,7 +1186,20 @@
         // 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 (!dimInLinearSpace && !equalsWithinMargin(1.0, layerDimmingRatio)) {
+                // If we don't dim in linear space, then when we gamma correct the dimming ratio we
+                // can assume a gamma 2.2 transfer function.
+                static constexpr float kInverseGamma22 = 1.f / 2.2f;
+                const auto gammaCorrectedDimmingRatio =
+                        std::pow(layerDimmingRatio, kInverseGamma22);
+                const auto dimmingMatrix =
+                        mat4::scale(vec4(gammaCorrectedDimmingRatio, gammaCorrectedDimmingRatio,
+                                         gammaCorrectedDimmingRatio, 1.f));
+                paint.setColorFilter(SkColorFilters::Matrix(
+                        toSkColorMatrix(display.colorTransform * dimmingMatrix)));
+            } else {
+                paint.setColorFilter(displayColorTransform);
+            }
         }
 
         if (!roundRectClip.isEmpty()) {
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h
index a650313..56815fe 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -180,6 +180,7 @@
 
     private:
         int mShadersCachedSinceLastCall = 0;
+        int mTotalShadersCompiled = 0;
     };
 
     SkSLCacheMonitor mSkSLCacheMonitor;
diff --git a/libs/renderengine/skia/filters/LinearEffect.cpp b/libs/renderengine/skia/filters/LinearEffect.cpp
index d479606..f7dcd3a 100644
--- a/libs/renderengine/skia/filters/LinearEffect.cpp
+++ b/libs/renderengine/skia/filters/LinearEffect.cpp
@@ -40,12 +40,11 @@
     return shader;
 }
 
-sk_sp<SkShader> createLinearEffectShader(sk_sp<SkShader> shader,
-                                         const shaders::LinearEffect& linearEffect,
-                                         sk_sp<SkRuntimeEffect> runtimeEffect,
-                                         const mat4& colorTransform, float maxDisplayLuminance,
-                                         float currentDisplayLuminanceNits, float maxLuminance,
-                                         AHardwareBuffer* buffer) {
+sk_sp<SkShader> createLinearEffectShader(
+        sk_sp<SkShader> shader, const shaders::LinearEffect& linearEffect,
+        sk_sp<SkRuntimeEffect> runtimeEffect, const mat4& colorTransform, float maxDisplayLuminance,
+        float currentDisplayLuminanceNits, float maxLuminance, AHardwareBuffer* buffer,
+        aidl::android::hardware::graphics::composer3::RenderIntent renderIntent) {
     ATRACE_CALL();
     SkRuntimeShaderBuilder effectBuilder(runtimeEffect);
 
@@ -53,7 +52,8 @@
 
     const auto uniforms =
             shaders::buildLinearEffectUniforms(linearEffect, colorTransform, maxDisplayLuminance,
-                                               currentDisplayLuminanceNits, maxLuminance, buffer);
+                                               currentDisplayLuminanceNits, maxLuminance, buffer,
+                                               renderIntent);
 
     for (const auto& uniform : uniforms) {
         effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size());
diff --git a/libs/renderengine/skia/filters/LinearEffect.h b/libs/renderengine/skia/filters/LinearEffect.h
index 26bae3b..3c66c51 100644
--- a/libs/renderengine/skia/filters/LinearEffect.h
+++ b/libs/renderengine/skia/filters/LinearEffect.h
@@ -42,12 +42,13 @@
 // 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 shaders::LinearEffect& linearEffect,
-                                         sk_sp<SkRuntimeEffect> runtimeEffect,
-                                         const mat4& colorTransform, float maxDisplayLuminance,
-                                         float currentDisplayLuminanceNits, float maxLuminance,
-                                         AHardwareBuffer* buffer);
+// * A RenderIntent that communicates the downstream renderintent for a physical display, for image
+// quality compensation.
+sk_sp<SkShader> createLinearEffectShader(
+        sk_sp<SkShader> inputShader, const shaders::LinearEffect& linearEffect,
+        sk_sp<SkRuntimeEffect> runtimeEffect, const mat4& colorTransform, float maxDisplayLuminance,
+        float currentDisplayLuminanceNits, float maxLuminance, AHardwareBuffer* buffer,
+        aidl::android::hardware::graphics::composer3::RenderIntent renderIntent);
 } // namespace skia
 } // namespace renderengine
 } // namespace android
diff --git a/libs/renderengine/tests/Android.bp b/libs/renderengine/tests/Android.bp
index d91af1e..e66fee1 100644
--- a/libs/renderengine/tests/Android.bp
+++ b/libs/renderengine/tests/Android.bp
@@ -47,6 +47,7 @@
     ],
 
     shared_libs: [
+        "android.hardware.graphics.composer3-V1-ndk",
         "libbase",
         "libcutils",
         "libEGL",
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 38ae2fd..2493242 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -2405,15 +2405,18 @@
 
 TEST_P(RenderEngineTest, testDimming) {
     if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) {
-        return;
+        GTEST_SKIP();
     }
+
     initializeRenderEngine();
 
+    const ui::Dataspace dataspace = ui::Dataspace::V0_SRGB_LINEAR;
+
     const auto displayRect = Rect(3, 1);
     const renderengine::DisplaySettings display{
             .physicalDisplay = displayRect,
             .clip = displayRect,
-            .outputDataspace = ui::Dataspace::V0_SRGB_LINEAR,
+            .outputDataspace = dataspace,
             .targetLuminanceNits = 1000.f,
     };
 
@@ -2432,7 +2435,7 @@
                                     },
                     },
             .alpha = 1.0f,
-            .sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR,
+            .sourceDataspace = dataspace,
             .whitePointNits = 200.f,
     };
 
@@ -2447,7 +2450,7 @@
                                     },
                     },
             .alpha = 1.0f,
-            .sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR,
+            .sourceDataspace = dataspace,
             .whitePointNits = 1000.f / 51.f,
     };
 
@@ -2462,7 +2465,7 @@
                                     },
                     },
             .alpha = 1.0f,
-            .sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR,
+            .sourceDataspace = dataspace,
             // 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,
@@ -2476,6 +2479,84 @@
     expectBufferColor(Rect(2, 0, 3, 1), 51, 0, 0, 255, 1);
 }
 
+TEST_P(RenderEngineTest, testDimming_inGammaSpace) {
+    if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) {
+        GTEST_SKIP();
+    }
+    initializeRenderEngine();
+
+    const ui::Dataspace dataspace = static_cast<ui::Dataspace>(ui::Dataspace::STANDARD_BT709 |
+                                                               ui::Dataspace::TRANSFER_GAMMA2_2 |
+                                                               ui::Dataspace::RANGE_FULL);
+
+    const auto displayRect = Rect(3, 1);
+    const renderengine::DisplaySettings display{
+            .physicalDisplay = displayRect,
+            .clip = displayRect,
+            .outputDataspace = dataspace,
+            .targetLuminanceNits = 1000.f,
+            .dimmingStage = aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF,
+    };
+
+    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 = dataspace,
+            .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 = dataspace,
+            .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 = dataspace,
+            // 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, 122, 0, 255, 1);
+    expectBufferColor(Rect(1, 0, 2, 1), 0, 0, 42, 255, 1);
+    expectBufferColor(Rect(2, 0, 3, 1), 122, 0, 0, 255, 1);
+}
+
 TEST_P(RenderEngineTest, testDimming_withoutTargetLuminance) {
     initializeRenderEngine();
     if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) {
diff --git a/libs/shaders/Android.bp b/libs/shaders/Android.bp
index 2f8bf49..6b936de 100644
--- a/libs/shaders/Android.bp
+++ b/libs/shaders/Android.bp
@@ -29,6 +29,7 @@
 
     shared_libs: [
         "android.hardware.graphics.common-V3-ndk",
+        "android.hardware.graphics.composer3-V1-ndk",
         "android.hardware.graphics.common@1.2",
         "libnativewindow",
     ],
diff --git a/libs/shaders/include/shaders/shaders.h b/libs/shaders/include/shaders/shaders.h
index 4ec7594..2a4a370 100644
--- a/libs/shaders/include/shaders/shaders.h
+++ b/libs/shaders/include/shaders/shaders.h
@@ -97,11 +97,10 @@
 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);
+std::vector<tonemap::ShaderUniform> buildLinearEffectUniforms(
+        const LinearEffect& linearEffect, const mat4& colorTransform, float maxDisplayLuminance,
+        float currentDisplayLuminanceNits, float maxLuminance, AHardwareBuffer* buffer = nullptr,
+        aidl::android::hardware::graphics::composer3::RenderIntent renderIntent =
+                aidl::android::hardware::graphics::composer3::RenderIntent::TONE_MAP_COLORIMETRIC);
 
 } // namespace android::shaders
diff --git a/libs/shaders/shaders.cpp b/libs/shaders/shaders.cpp
index 5935589..f0d45c2 100644
--- a/libs/shaders/shaders.cpp
+++ b/libs/shaders/shaders.cpp
@@ -464,12 +464,10 @@
 }
 
 // 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> buildLinearEffectUniforms(
+        const LinearEffect& linearEffect, const mat4& colorTransform, float maxDisplayLuminance,
+        float currentDisplayLuminanceNits, float maxLuminance, AHardwareBuffer* buffer,
+        aidl::android::hardware::graphics::composer3::RenderIntent renderIntent) {
     std::vector<tonemap::ShaderUniform> uniforms;
     if (linearEffect.inputDataspace == linearEffect.outputDataspace) {
         uniforms.push_back({.name = "in_rgbToXyz", .value = buildUniformValue<mat4>(mat4())});
@@ -495,7 +493,8 @@
                                .currentDisplayLuminance = currentDisplayLuminanceNits > 0
                                        ? currentDisplayLuminanceNits
                                        : maxDisplayLuminance,
-                               .buffer = buffer};
+                               .buffer = buffer,
+                               .renderIntent = renderIntent};
 
     for (const auto uniform : tonemap::getToneMapper()->generateShaderSkSLUniforms(metadata)) {
         uniforms.push_back(uniform);
diff --git a/libs/tonemap/Android.bp b/libs/tonemap/Android.bp
index dc55586..37c9824 100644
--- a/libs/tonemap/Android.bp
+++ b/libs/tonemap/Android.bp
@@ -29,6 +29,7 @@
 
     shared_libs: [
         "android.hardware.graphics.common-V3-ndk",
+        "android.hardware.graphics.composer3-V1-ndk",
         "liblog",
         "libnativewindow",
     ],
diff --git a/libs/tonemap/include/tonemap/tonemap.h b/libs/tonemap/include/tonemap/tonemap.h
index c51016d..852fc87 100644
--- a/libs/tonemap/include/tonemap/tonemap.h
+++ b/libs/tonemap/include/tonemap/tonemap.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <aidl/android/hardware/graphics/common/Dataspace.h>
+#include <aidl/android/hardware/graphics/composer3/RenderIntent.h>
 #include <android/hardware_buffer.h>
 #include <math/vec3.h>
 
@@ -41,7 +42,7 @@
 
 // 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.
+// as otherwise caching by other parts of the system using these shaders may break.
 struct Metadata {
     // The maximum luminance of the display in nits
     float displayMaxLuminance = 0.0;
@@ -61,6 +62,17 @@
     // texture that does not have associated metadata. As such, implementations
     // must support nullptr.
     AHardwareBuffer* buffer = nullptr;
+
+    // RenderIntent of the destination display.
+    // Non-colorimetric render-intents may be defined in order to take advantage of the full display
+    // gamut. Various contrast-enhancement mechanisms may be employed on SDR content as a result,
+    // which means that HDR content may need to be compensated in order to achieve correct blending
+    // behavior. This default is effectively optional - the display render intent may not be
+    // available to clients such as HWUI which are display-agnostic. For those clients, tone-map
+    // colorimetric may be assumed so that the luminance range may be converted to the correct range
+    // based on the output dataspace.
+    aidl::android::hardware::graphics::composer3::RenderIntent renderIntent =
+            aidl::android::hardware::graphics::composer3::RenderIntent::TONE_MAP_COLORIMETRIC;
 };
 
 // Utility class containing pre-processed conversions for a particular color
diff --git a/libs/tonemap/tests/Android.bp b/libs/tonemap/tests/Android.bp
index 26a1d79..58851b4 100644
--- a/libs/tonemap/tests/Android.bp
+++ b/libs/tonemap/tests/Android.bp
@@ -32,6 +32,7 @@
     ],
     shared_libs: [
         "android.hardware.graphics.common-V3-ndk",
+        "android.hardware.graphics.composer3-V1-ndk",
         "libnativewindow",
     ],
     static_libs: [
diff --git a/libs/ui/DeviceProductInfo.cpp b/libs/ui/DeviceProductInfo.cpp
index 4d6ce43..04d9d3c 100644
--- a/libs/ui/DeviceProductInfo.cpp
+++ b/libs/ui/DeviceProductInfo.cpp
@@ -57,7 +57,7 @@
 }
 
 void DeviceProductInfo::dump(std::string& result) const {
-    StringAppendF(&result, "{name=%s, ", name.c_str());
+    StringAppendF(&result, "{name=\"%s\", ", name.c_str());
     StringAppendF(&result, "manufacturerPnpId=%s, ", manufacturerPnpId.data());
     StringAppendF(&result, "productId=%s, ", productId.c_str());
 
diff --git a/libs/ui/Gralloc4.cpp b/libs/ui/Gralloc4.cpp
index 1a42642..4f950b8 100644
--- a/libs/ui/Gralloc4.cpp
+++ b/libs/ui/Gralloc4.cpp
@@ -22,6 +22,7 @@
 #include <aidlcommonsupport/NativeHandle.h>
 #include <android/binder_enums.h>
 #include <android/binder_manager.h>
+#include <gralloctypes/Gralloc4.h>
 #include <hidl/ServiceManagement.h>
 #include <hwbinder/IPCThreadState.h>
 #include <ui/Gralloc4.h>
@@ -358,20 +359,19 @@
             if (!gralloc4::isStandardPlaneLayoutComponentType(planeLayoutComponent.type)) {
                 continue;
             }
-            if (0 != planeLayoutComponent.offsetInBits % 8) {
-                unlock(bufferHandle);
-                return BAD_VALUE;
-            }
 
-            uint8_t* tmpData = static_cast<uint8_t*>(data) + planeLayout.offsetInBytes +
-                    (planeLayoutComponent.offsetInBits / 8);
+            uint8_t* tmpData = static_cast<uint8_t*>(data) + planeLayout.offsetInBytes;
+
+            // Note that `offsetInBits` may not be a multiple of 8 for packed formats (e.g. P010)
+            // but we still want to point to the start of the first byte.
+            tmpData += (planeLayoutComponent.offsetInBits / 8);
+
             uint64_t sampleIncrementInBytes;
 
             auto type = static_cast<PlaneLayoutComponentType>(planeLayoutComponent.type.value);
             switch (type) {
                 case PlaneLayoutComponentType::Y:
-                    if ((ycbcr.y != nullptr) || (planeLayoutComponent.sizeInBits != 8) ||
-                        (planeLayout.sampleIncrementInBits != 8)) {
+                    if ((ycbcr.y != nullptr) || (planeLayout.sampleIncrementInBits % 8 != 0)) {
                         unlock(bufferHandle);
                         return BAD_VALUE;
                     }
@@ -387,7 +387,8 @@
                     }
 
                     sampleIncrementInBytes = planeLayout.sampleIncrementInBits / 8;
-                    if ((sampleIncrementInBytes != 1) && (sampleIncrementInBytes != 2)) {
+                    if ((sampleIncrementInBytes != 1) && (sampleIncrementInBytes != 2) &&
+                        (sampleIncrementInBytes != 4)) {
                         unlock(bufferHandle);
                         return BAD_VALUE;
                     }
@@ -524,6 +525,37 @@
     return decodeFunction(vec, outMetadata);
 }
 
+template <class T>
+status_t Gralloc4Mapper::set(buffer_handle_t bufferHandle, const MetadataType& metadataType,
+                             const T& metadata, EncodeFunction<T> encodeFunction) const {
+    hidl_vec<uint8_t> encodedMetadata;
+    if (const status_t status = encodeFunction(metadata, &encodedMetadata); status != OK) {
+        ALOGE("Encoding metadata(%s) failed with %d", metadataType.name.c_str(), status);
+        return status;
+    }
+    hidl_vec<uint8_t> vec;
+    auto ret =
+            mMapper->set(const_cast<native_handle_t*>(bufferHandle), metadataType, encodedMetadata);
+
+    const Error error = ret.withDefault(kTransactionError);
+    switch (error) {
+        case Error::BAD_DESCRIPTOR:
+        case Error::BAD_BUFFER:
+        case Error::BAD_VALUE:
+        case Error::NO_RESOURCES:
+            ALOGE("set(%s, %" PRIu64 ", ...) failed with %d", metadataType.name.c_str(),
+                  metadataType.value, error);
+            break;
+        // It is not an error to attempt to set metadata that a particular gralloc implementation
+        // happens to not support.
+        case Error::UNSUPPORTED:
+        case Error::NONE:
+            break;
+    }
+
+    return static_cast<status_t>(error);
+}
+
 status_t Gralloc4Mapper::getBufferId(buffer_handle_t bufferHandle, uint64_t* outBufferId) const {
     return get(bufferHandle, gralloc4::MetadataType_BufferId, gralloc4::decodeBufferId,
                outBufferId);
@@ -673,6 +705,12 @@
     return NO_ERROR;
 }
 
+status_t Gralloc4Mapper::setDataspace(buffer_handle_t bufferHandle, ui::Dataspace dataspace) const {
+    return set(bufferHandle, gralloc4::MetadataType_Dataspace,
+               static_cast<aidl::android::hardware::graphics::common::Dataspace>(dataspace),
+               gralloc4::encodeDataspace);
+}
+
 status_t Gralloc4Mapper::getBlendMode(buffer_handle_t bufferHandle,
                                       ui::BlendMode* outBlendMode) const {
     return get(bufferHandle, gralloc4::MetadataType_BlendMode, gralloc4::decodeBlendMode,
@@ -685,24 +723,47 @@
                outSmpte2086);
 }
 
+status_t Gralloc4Mapper::setSmpte2086(buffer_handle_t bufferHandle,
+                                      std::optional<ui::Smpte2086> smpte2086) const {
+    return set(bufferHandle, gralloc4::MetadataType_Smpte2086, smpte2086,
+               gralloc4::encodeSmpte2086);
+}
+
 status_t Gralloc4Mapper::getCta861_3(buffer_handle_t bufferHandle,
                                      std::optional<ui::Cta861_3>* outCta861_3) const {
     return get(bufferHandle, gralloc4::MetadataType_Cta861_3, gralloc4::decodeCta861_3,
                outCta861_3);
 }
 
+status_t Gralloc4Mapper::setCta861_3(buffer_handle_t bufferHandle,
+                                     std::optional<ui::Cta861_3> cta861_3) const {
+    return set(bufferHandle, gralloc4::MetadataType_Cta861_3, cta861_3, gralloc4::encodeCta861_3);
+}
+
 status_t Gralloc4Mapper::getSmpte2094_40(
         buffer_handle_t bufferHandle, std::optional<std::vector<uint8_t>>* outSmpte2094_40) const {
     return get(bufferHandle, gralloc4::MetadataType_Smpte2094_40, gralloc4::decodeSmpte2094_40,
                outSmpte2094_40);
 }
 
+status_t Gralloc4Mapper::setSmpte2094_40(buffer_handle_t bufferHandle,
+                                         std::optional<std::vector<uint8_t>> smpte2094_40) const {
+    return set(bufferHandle, gralloc4::MetadataType_Smpte2094_40, smpte2094_40,
+               gralloc4::encodeSmpte2094_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);
 }
 
+status_t Gralloc4Mapper::setSmpte2094_10(buffer_handle_t bufferHandle,
+                                         std::optional<std::vector<uint8_t>> smpte2094_10) const {
+    return set(bufferHandle, gralloc4::MetadataType_Smpte2094_10, smpte2094_10,
+               gralloc4::encodeSmpte2094_10);
+}
+
 template <class T>
 status_t Gralloc4Mapper::getDefault(uint32_t width, uint32_t height, PixelFormat format,
                                     uint32_t layerCount, uint64_t usage,
@@ -1132,10 +1193,6 @@
 
 Gralloc4Allocator::Gralloc4Allocator(const Gralloc4Mapper& mapper) : mMapper(mapper) {
     mAllocator = IAllocator::getService();
-    if (mAllocator == nullptr) {
-        ALOGW("allocator 4.x is not supported");
-        return;
-    }
     if (__builtin_available(android 31, *)) {
         if (hasIAllocatorAidl()) {
             mAidlAllocator = AidlIAllocator::fromBinder(ndk::SpAIBinder(
@@ -1143,10 +1200,14 @@
             ALOGE_IF(!mAidlAllocator, "AIDL IAllocator declared but failed to get service");
         }
     }
+    if (mAllocator == nullptr && mAidlAllocator == nullptr) {
+        ALOGW("allocator 4.x is not supported");
+        return;
+    }
 }
 
 bool Gralloc4Allocator::isLoaded() const {
-    return mAllocator != nullptr;
+    return mAllocator != nullptr || mAidlAllocator != nullptr;
 }
 
 std::string Gralloc4Allocator::dumpDebugInfo(bool less) const {
diff --git a/libs/ui/GraphicBufferMapper.cpp b/libs/ui/GraphicBufferMapper.cpp
index 82d6cd5..a98e697 100644
--- a/libs/ui/GraphicBufferMapper.cpp
+++ b/libs/ui/GraphicBufferMapper.cpp
@@ -281,6 +281,10 @@
     return mMapper->getDataspace(bufferHandle, outDataspace);
 }
 
+status_t GraphicBufferMapper::setDataspace(buffer_handle_t bufferHandle, ui::Dataspace dataspace) {
+    return mMapper->setDataspace(bufferHandle, dataspace);
+}
+
 status_t GraphicBufferMapper::getBlendMode(buffer_handle_t bufferHandle,
                                            ui::BlendMode* outBlendMode) {
     return mMapper->getBlendMode(bufferHandle, outBlendMode);
@@ -291,21 +295,41 @@
     return mMapper->getSmpte2086(bufferHandle, outSmpte2086);
 }
 
+status_t GraphicBufferMapper::setSmpte2086(buffer_handle_t bufferHandle,
+                                           std::optional<ui::Smpte2086> smpte2086) {
+    return mMapper->setSmpte2086(bufferHandle, smpte2086);
+}
+
 status_t GraphicBufferMapper::getCta861_3(buffer_handle_t bufferHandle,
                                           std::optional<ui::Cta861_3>* outCta861_3) {
     return mMapper->getCta861_3(bufferHandle, outCta861_3);
 }
 
+status_t GraphicBufferMapper::setCta861_3(buffer_handle_t bufferHandle,
+                                          std::optional<ui::Cta861_3> cta861_3) {
+    return mMapper->setCta861_3(bufferHandle, cta861_3);
+}
+
 status_t GraphicBufferMapper::getSmpte2094_40(
         buffer_handle_t bufferHandle, std::optional<std::vector<uint8_t>>* outSmpte2094_40) {
     return mMapper->getSmpte2094_40(bufferHandle, outSmpte2094_40);
 }
 
+status_t GraphicBufferMapper::setSmpte2094_40(buffer_handle_t bufferHandle,
+                                              std::optional<std::vector<uint8_t>> smpte2094_40) {
+    return mMapper->setSmpte2094_40(bufferHandle, smpte2094_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::setSmpte2094_10(buffer_handle_t bufferHandle,
+                                              std::optional<std::vector<uint8_t>> smpte2094_10) {
+    return mMapper->setSmpte2094_10(bufferHandle, smpte2094_10);
+}
+
 status_t GraphicBufferMapper::getDefaultPixelFormatFourCC(uint32_t width, uint32_t height,
                                                           PixelFormat format, uint32_t layerCount,
                                                           uint64_t usage,
diff --git a/libs/ui/include/ui/Gralloc.h b/libs/ui/include/ui/Gralloc.h
index 753b0a6..6101d4b 100644
--- a/libs/ui/include/ui/Gralloc.h
+++ b/libs/ui/include/ui/Gralloc.h
@@ -161,6 +161,10 @@
                                   ui::Dataspace* /*outDataspace*/) const {
         return INVALID_OPERATION;
     }
+    virtual status_t setDataspace(buffer_handle_t /*bufferHandle*/,
+                                  ui::Dataspace /*dataspace*/) const {
+        return INVALID_OPERATION;
+    }
     virtual status_t getBlendMode(buffer_handle_t /*bufferHandle*/,
                                   ui::BlendMode* /*outBlendMode*/) const {
         return INVALID_OPERATION;
@@ -169,21 +173,36 @@
                                   std::optional<ui::Smpte2086>* /*outSmpte2086*/) const {
         return INVALID_OPERATION;
     }
+    virtual status_t setSmpte2086(buffer_handle_t /*bufferHandle*/,
+                                  std::optional<ui::Smpte2086> /*smpte2086*/) const {
+        return INVALID_OPERATION;
+    }
     virtual status_t getCta861_3(buffer_handle_t /*bufferHandle*/,
                                  std::optional<ui::Cta861_3>* /*outCta861_3*/) const {
         return INVALID_OPERATION;
     }
+    virtual status_t setCta861_3(buffer_handle_t /*bufferHandle*/,
+                                 std::optional<ui::Cta861_3> /*cta861_3*/) const {
+        return INVALID_OPERATION;
+    }
     virtual status_t getSmpte2094_40(
             buffer_handle_t /*bufferHandle*/,
             std::optional<std::vector<uint8_t>>* /*outSmpte2094_40*/) const {
         return INVALID_OPERATION;
     }
+    virtual status_t setSmpte2094_40(buffer_handle_t /*bufferHandle*/,
+                                     std::optional<std::vector<uint8_t>> /*smpte2094_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 setSmpte2094_10(buffer_handle_t /*bufferHandle*/,
+                                     std::optional<std::vector<uint8_t>> /*smpte2094_10*/) const {
+        return INVALID_OPERATION;
+    }
     virtual status_t getDefaultPixelFormatFourCC(uint32_t /*width*/, uint32_t /*height*/,
                                                  PixelFormat /*format*/, uint32_t /*layerCount*/,
                                                  uint64_t /*usage*/,
diff --git a/libs/ui/include/ui/Gralloc4.h b/libs/ui/include/ui/Gralloc4.h
index fe38709..cf023c9 100644
--- a/libs/ui/include/ui/Gralloc4.h
+++ b/libs/ui/include/ui/Gralloc4.h
@@ -102,16 +102,24 @@
     status_t getPlaneLayouts(buffer_handle_t bufferHandle,
                              std::vector<ui::PlaneLayout>* outPlaneLayouts) const override;
     status_t getDataspace(buffer_handle_t bufferHandle, ui::Dataspace* outDataspace) const override;
+    status_t setDataspace(buffer_handle_t bufferHandle, ui::Dataspace dataspace) const override;
     status_t getBlendMode(buffer_handle_t bufferHandle, ui::BlendMode* outBlendMode) const override;
     status_t getSmpte2086(buffer_handle_t bufferHandle,
                           std::optional<ui::Smpte2086>* outSmpte2086) const override;
+    status_t setSmpte2086(buffer_handle_t bufferHandle,
+                          std::optional<ui::Smpte2086> smpte2086) const override;
     status_t getCta861_3(buffer_handle_t bufferHandle,
                          std::optional<ui::Cta861_3>* outCta861_3) const override;
+    status_t setCta861_3(buffer_handle_t bufferHandle,
+                         std::optional<ui::Cta861_3> cta861_3) const override;
     status_t getSmpte2094_40(buffer_handle_t bufferHandle,
                              std::optional<std::vector<uint8_t>>* outSmpte2094_40) const override;
+    status_t setSmpte2094_40(buffer_handle_t bufferHandle,
+                             std::optional<std::vector<uint8_t>> smpte2094_40) const override;
     status_t getSmpte2094_10(buffer_handle_t bufferHandle,
                              std::optional<std::vector<uint8_t>>* outSmpte2094_10) const override;
-
+    status_t setSmpte2094_10(buffer_handle_t bufferHandle,
+                             std::optional<std::vector<uint8_t>> smpte2094_10) const override;
     status_t getDefaultPixelFormatFourCC(uint32_t width, uint32_t height, PixelFormat format,
                                          uint32_t layerCount, uint64_t usage,
                                          uint32_t* outPixelFormatFourCC) const override;
@@ -157,6 +165,8 @@
 
     template <class T>
     using DecodeFunction = status_t (*)(const hardware::hidl_vec<uint8_t>& input, T* output);
+    template <class T>
+    using EncodeFunction = status_t (*)(const T& input, hardware::hidl_vec<uint8_t>* output);
 
     template <class T>
     status_t get(
@@ -165,6 +175,12 @@
             DecodeFunction<T> decodeFunction, T* outMetadata) const;
 
     template <class T>
+    status_t set(
+            buffer_handle_t bufferHandle,
+            const android::hardware::graphics::mapper::V4_0::IMapper::MetadataType& metadataType,
+            const T& metadata, EncodeFunction<T> encodeFunction) const;
+
+    template <class T>
     status_t getDefault(
             uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
             uint64_t usage,
diff --git a/libs/ui/include/ui/GraphicBufferMapper.h b/libs/ui/include/ui/GraphicBufferMapper.h
index 257c155..507fa35 100644
--- a/libs/ui/include/ui/GraphicBufferMapper.h
+++ b/libs/ui/include/ui/GraphicBufferMapper.h
@@ -121,13 +121,20 @@
     status_t getPlaneLayouts(buffer_handle_t bufferHandle,
                              std::vector<ui::PlaneLayout>* outPlaneLayouts);
     status_t getDataspace(buffer_handle_t bufferHandle, ui::Dataspace* outDataspace);
+    status_t setDataspace(buffer_handle_t bufferHandle, ui::Dataspace dataspace);
     status_t getBlendMode(buffer_handle_t bufferHandle, ui::BlendMode* outBlendMode);
     status_t getSmpte2086(buffer_handle_t bufferHandle, std::optional<ui::Smpte2086>* outSmpte2086);
+    status_t setSmpte2086(buffer_handle_t bufferHandle, std::optional<ui::Smpte2086> smpte2086);
     status_t getCta861_3(buffer_handle_t bufferHandle, std::optional<ui::Cta861_3>* outCta861_3);
+    status_t setCta861_3(buffer_handle_t bufferHandle, std::optional<ui::Cta861_3> cta861_3);
     status_t getSmpte2094_40(buffer_handle_t bufferHandle,
                              std::optional<std::vector<uint8_t>>* outSmpte2094_40);
+    status_t setSmpte2094_40(buffer_handle_t bufferHandle,
+                             std::optional<std::vector<uint8_t>> smpte2094_40);
     status_t getSmpte2094_10(buffer_handle_t bufferHandle,
                              std::optional<std::vector<uint8_t>>* outSmpte2094_10);
+    status_t setSmpte2094_10(buffer_handle_t bufferHandle,
+                             std::optional<std::vector<uint8_t>> smpte2094_10);
 
     /**
      * Gets the default metadata for a gralloc buffer allocated with the given parameters.
diff --git a/libs/ui/include/ui/GraphicTypes.h b/libs/ui/include/ui/GraphicTypes.h
index 4bdacb0..8661c36 100644
--- a/libs/ui/include/ui/GraphicTypes.h
+++ b/libs/ui/include/ui/GraphicTypes.h
@@ -62,5 +62,23 @@
 using Compression = aidl::android::hardware::graphics::common::Compression;
 using Interlaced = aidl::android::hardware::graphics::common::Interlaced;
 
+inline aidl::android::hardware::graphics::common::XyColor translate(const android_xy_color& color) {
+    return aidl::android::hardware::graphics::common::XyColor{.x = color.x, .y = color.y};
+}
+
+inline Smpte2086 translate(const android_smpte2086_metadata& metadata) {
+    return Smpte2086{.primaryRed = translate(metadata.displayPrimaryRed),
+                     .primaryGreen = translate(metadata.displayPrimaryGreen),
+                     .primaryBlue = translate(metadata.displayPrimaryBlue),
+                     .whitePoint = translate(metadata.whitePoint),
+                     .maxLuminance = metadata.maxLuminance,
+                     .minLuminance = metadata.minLuminance};
+}
+
+inline Cta861_3 translate(const android_cta861_3_metadata& metadata) {
+    return Cta861_3{.maxContentLightLevel = metadata.maxContentLightLevel,
+                    .maxFrameAverageLightLevel = metadata.maxFrameAverageLightLevel};
+}
+
 }  // namespace ui
 }  // namespace android
diff --git a/libs/ui/include/ui/Size.h b/libs/ui/include/ui/Size.h
index ecc192d..bdcbd56 100644
--- a/libs/ui/include/ui/Size.h
+++ b/libs/ui/include/ui/Size.h
@@ -23,6 +23,8 @@
 #include <type_traits>
 #include <utility>
 
+#include <ui/Rotation.h>
+
 namespace android::ui {
 
 // A simple value type representing a two-dimensional size.
@@ -61,6 +63,16 @@
         set(Size(w, h));
     }
 
+    // Applies a rotation onto the size
+    void rotate(Rotation rotation) {
+        if (rotation == ROTATION_90 || rotation == ROTATION_270) {
+            transpose();
+        }
+    }
+
+    // Swaps the width and height, emulating a 90 degree rotation.
+    void transpose() { std::swap(width, height); }
+
     // Sets the value to kInvalidSize
     void makeInvalid();
 
diff --git a/libs/ui/tests/Android.bp b/libs/ui/tests/Android.bp
index 22fbf45..831b64d 100644
--- a/libs/ui/tests/Android.bp
+++ b/libs/ui/tests/Android.bp
@@ -114,7 +114,6 @@
     ],
     shared_libs: [
         "libbinder",
-        "libgui",
         "liblog",
         "libui",
         "libutils",
diff --git a/libs/ui/tests/GraphicBufferOverBinder_test.cpp b/libs/ui/tests/GraphicBufferOverBinder_test.cpp
index 126a945..4c9d574 100644
--- a/libs/ui/tests/GraphicBufferOverBinder_test.cpp
+++ b/libs/ui/tests/GraphicBufferOverBinder_test.cpp
@@ -20,9 +20,6 @@
 #include <binder/Parcel.h>
 #include <binder/ProcessState.h>
 #include <gtest/gtest.h>
-#include <gui/BufferQueue.h>
-#include <gui/IGraphicBufferConsumer.h>
-#include <gui/IGraphicBufferProducer.h>
 #include <ui/GraphicBuffer.h>
 #include <utils/Log.h>
 
diff --git a/libs/ui/tests/Size_test.cpp b/libs/ui/tests/Size_test.cpp
index acef47f..0a236e6 100644
--- a/libs/ui/tests/Size_test.cpp
+++ b/libs/ui/tests/Size_test.cpp
@@ -61,6 +61,38 @@
     EXPECT_FALSE(Size(1, 1) < Size(1, 1));
 }
 
+TEST(SizeTest, Transpose) {
+    Size s(123, 456);
+    s.transpose();
+    EXPECT_EQ(s, Size(456, 123));
+}
+
+TEST(SizeTest, Rotate) {
+    {
+        Size s(123, 456);
+        s.rotate(Rotation::Rotation0);
+        EXPECT_EQ(s, Size(123, 456));
+    }
+
+    {
+        Size s(123, 456);
+        s.rotate(Rotation::Rotation90);
+        EXPECT_EQ(s, Size(456, 123));
+    }
+
+    {
+        Size s(123, 456);
+        s.rotate(Rotation::Rotation180);
+        EXPECT_EQ(s, Size(123, 456));
+    }
+
+    {
+        Size s(123, 456);
+        s.rotate(Rotation::Rotation270);
+        EXPECT_EQ(s, Size(456, 123));
+    }
+}
+
 TEST(SizeTest, ValidAndEmpty) {
     {
         Size s;
diff --git a/libs/vr/libbroadcastring/Android.bp b/libs/vr/libbroadcastring/Android.bp
index d4538f1..fa449ae 100644
--- a/libs/vr/libbroadcastring/Android.bp
+++ b/libs/vr/libbroadcastring/Android.bp
@@ -9,7 +9,7 @@
 
 cc_library_static {
     name: "libbroadcastring",
-    clang: true,
+
     cflags: [
         "-Wall",
         "-Wextra",
diff --git a/services/gpuservice/gpuwork/bpfprogs/gpu_work.c b/services/gpuservice/gpuwork/bpfprogs/gpu_work.c
index c8e79a2..95cfae7 100644
--- a/services/gpuservice/gpuwork/bpfprogs/gpu_work.c
+++ b/services/gpuservice/gpuwork/bpfprogs/gpu_work.c
@@ -71,11 +71,14 @@
 //
 // 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
+// "double-counting" parallel GPU work on the same GPU for the same |uid|. Note
+// that even if the parallel GPU work was submitted from several different
+// processes (i.e. different PIDs) with the same UID, this overlapping work must
+// not be double-counted, as it still came from a single |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:
+// following for a single |uid|:
 //  - A period has:
 //    - |start_time_ns|: 100,000,000 ns
 //    - |end_time_ns|:   800,000,000 ns
@@ -99,16 +102,20 @@
 //  - 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.
+// Thus, the |total_active_duration_ns| is the sum of these two
+// (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.
+//
+// Protected mode: protected GPU work must not be reported. Periods must be
+// emitted, and the |total_active_duration_ns| value set, as if the protected
+// GPU work did not occur.
 //
 // 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
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index ed9bfd2..41878e3 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -58,6 +58,7 @@
     srcs: [
         "InputClassifier.cpp",
         "InputCommonConverter.cpp",
+        "PreferStylusOverTouchBlocker.cpp",
         "UnwantedInteractionBlocker.cpp",
         "InputManager.cpp",
     ],
diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp
index 3a4b6c5..2a3924b 100644
--- a/services/inputflinger/InputListener.cpp
+++ b/services/inputflinger/InputListener.cpp
@@ -202,9 +202,11 @@
         coords += "}";
     }
     return StringPrintf("NotifyMotionArgs(id=%" PRId32 ", eventTime=%" PRId64 ", deviceId=%" PRId32
-                        ", source=%s, action=%s, pointerCount=%" PRIu32 " pointers=%s)",
+                        ", source=%s, action=%s, pointerCount=%" PRIu32
+                        " pointers=%s, flags=0x%08x)",
                         id, eventTime, deviceId, inputEventSourceToString(source).c_str(),
-                        MotionEvent::actionToString(action).c_str(), pointerCount, coords.c_str());
+                        MotionEvent::actionToString(action).c_str(), pointerCount, coords.c_str(),
+                        flags);
 }
 
 void NotifyMotionArgs::notify(InputListenerInterface& listener) const {
diff --git a/services/inputflinger/PreferStylusOverTouchBlocker.cpp b/services/inputflinger/PreferStylusOverTouchBlocker.cpp
new file mode 100644
index 0000000..beec2e1
--- /dev/null
+++ b/services/inputflinger/PreferStylusOverTouchBlocker.cpp
@@ -0,0 +1,216 @@
+/*
+ * 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 "PreferStylusOverTouchBlocker.h"
+#include <input/PrintTools.h>
+
+namespace android {
+
+static std::pair<bool, bool> checkToolType(const NotifyMotionArgs& args) {
+    bool hasStylus = false;
+    bool hasTouch = false;
+    for (size_t i = 0; i < args.pointerCount; i++) {
+        // Make sure we are canceling stylus pointers
+        const int32_t toolType = args.pointerProperties[i].toolType;
+        if (toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS ||
+            toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) {
+            hasStylus = true;
+        }
+        if (toolType == AMOTION_EVENT_TOOL_TYPE_FINGER) {
+            hasTouch = true;
+        }
+    }
+    return std::make_pair(hasTouch, hasStylus);
+}
+
+/**
+ * Intersect two sets in-place, storing the result in 'set1'.
+ * Find elements in set1 that are not present in set2 and delete them,
+ * relying on the fact that the two sets are ordered.
+ */
+template <typename T>
+static void intersectInPlace(std::set<T>& set1, const std::set<T>& set2) {
+    typename std::set<T>::iterator it1 = set1.begin();
+    typename std::set<T>::const_iterator it2 = set2.begin();
+    while (it1 != set1.end() && it2 != set2.end()) {
+        const T& element1 = *it1;
+        const T& element2 = *it2;
+        if (element1 < element2) {
+            // This element is not present in set2. Remove it from set1.
+            it1 = set1.erase(it1);
+            continue;
+        }
+        if (element2 < element1) {
+            it2++;
+        }
+        if (element1 == element2) {
+            it1++;
+            it2++;
+        }
+    }
+    // Remove the rest of the elements in set1 because set2 is already exhausted.
+    set1.erase(it1, set1.end());
+}
+
+/**
+ * Same as above, but prune a map
+ */
+template <typename K, class V>
+static void intersectInPlace(std::map<K, V>& map, const std::set<K>& set2) {
+    typename std::map<K, V>::iterator it1 = map.begin();
+    typename std::set<K>::const_iterator it2 = set2.begin();
+    while (it1 != map.end() && it2 != set2.end()) {
+        const auto& [key, _] = *it1;
+        const K& element2 = *it2;
+        if (key < element2) {
+            // This element is not present in set2. Remove it from map.
+            it1 = map.erase(it1);
+            continue;
+        }
+        if (element2 < key) {
+            it2++;
+        }
+        if (key == element2) {
+            it1++;
+            it2++;
+        }
+    }
+    // Remove the rest of the elements in map because set2 is already exhausted.
+    map.erase(it1, map.end());
+}
+
+// -------------------------------- PreferStylusOverTouchBlocker -----------------------------------
+
+std::vector<NotifyMotionArgs> PreferStylusOverTouchBlocker::processMotion(
+        const NotifyMotionArgs& args) {
+    const auto [hasTouch, hasStylus] = checkToolType(args);
+    const bool isUpOrCancel =
+            args.action == AMOTION_EVENT_ACTION_UP || args.action == AMOTION_EVENT_ACTION_CANCEL;
+
+    if (hasTouch && hasStylus) {
+        mDevicesWithMixedToolType.insert(args.deviceId);
+    }
+    // Handle the case where mixed touch and stylus pointers are reported. Add this device to the
+    // ignore list, since it clearly supports simultaneous touch and stylus.
+    if (mDevicesWithMixedToolType.find(args.deviceId) != mDevicesWithMixedToolType.end()) {
+        // This event comes from device with mixed stylus and touch event. Ignore this device.
+        if (mCanceledDevices.find(args.deviceId) != mCanceledDevices.end()) {
+            // If we started to cancel events from this device, continue to do so to keep
+            // the stream consistent. It should happen at most once per "mixed" device.
+            if (isUpOrCancel) {
+                mCanceledDevices.erase(args.deviceId);
+                mLastTouchEvents.erase(args.deviceId);
+            }
+            return {};
+        }
+        return {args};
+    }
+
+    const bool isStylusEvent = hasStylus;
+    const bool isDown = args.action == AMOTION_EVENT_ACTION_DOWN;
+
+    if (isStylusEvent) {
+        if (isDown) {
+            // Reject all touch while stylus is down
+            mActiveStyli.insert(args.deviceId);
+
+            // Cancel all current touch!
+            std::vector<NotifyMotionArgs> result;
+            for (auto& [deviceId, lastTouchEvent] : mLastTouchEvents) {
+                if (mCanceledDevices.find(deviceId) != mCanceledDevices.end()) {
+                    // Already canceled, go to next one.
+                    continue;
+                }
+                // Not yet canceled. Cancel it.
+                lastTouchEvent.action = AMOTION_EVENT_ACTION_CANCEL;
+                lastTouchEvent.flags |= AMOTION_EVENT_FLAG_CANCELED;
+                lastTouchEvent.eventTime = systemTime(SYSTEM_TIME_MONOTONIC);
+                result.push_back(lastTouchEvent);
+                mCanceledDevices.insert(deviceId);
+            }
+            result.push_back(args);
+            return result;
+        }
+        if (isUpOrCancel) {
+            mActiveStyli.erase(args.deviceId);
+        }
+        // Never drop stylus events
+        return {args};
+    }
+
+    const bool isTouchEvent = hasTouch;
+    if (isTouchEvent) {
+        // Suppress the current gesture if any stylus is still down
+        if (!mActiveStyli.empty()) {
+            mCanceledDevices.insert(args.deviceId);
+        }
+
+        const bool shouldDrop = mCanceledDevices.find(args.deviceId) != mCanceledDevices.end();
+        if (isUpOrCancel) {
+            mCanceledDevices.erase(args.deviceId);
+            mLastTouchEvents.erase(args.deviceId);
+        }
+
+        // If we already canceled the current gesture, then continue to drop events from it, even if
+        // the stylus has been lifted.
+        if (shouldDrop) {
+            return {};
+        }
+
+        if (!isUpOrCancel) {
+            mLastTouchEvents[args.deviceId] = args;
+        }
+        return {args};
+    }
+
+    // Not a touch or stylus event
+    return {args};
+}
+
+void PreferStylusOverTouchBlocker::notifyInputDevicesChanged(
+        const std::vector<InputDeviceInfo>& inputDevices) {
+    std::set<int32_t> presentDevices;
+    for (const InputDeviceInfo& device : inputDevices) {
+        presentDevices.insert(device.getId());
+    }
+    // Only keep the devices that are still present.
+    intersectInPlace(mDevicesWithMixedToolType, presentDevices);
+    intersectInPlace(mLastTouchEvents, presentDevices);
+    intersectInPlace(mCanceledDevices, presentDevices);
+    intersectInPlace(mActiveStyli, presentDevices);
+}
+
+void PreferStylusOverTouchBlocker::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
+    mDevicesWithMixedToolType.erase(args.deviceId);
+    mLastTouchEvents.erase(args.deviceId);
+    mCanceledDevices.erase(args.deviceId);
+    mActiveStyli.erase(args.deviceId);
+}
+
+static std::string dumpArgs(const NotifyMotionArgs& args) {
+    return args.dump();
+}
+
+std::string PreferStylusOverTouchBlocker::dump() const {
+    std::string out;
+    out += "mActiveStyli: " + dumpSet(mActiveStyli) + "\n";
+    out += "mLastTouchEvents: " + dumpMap(mLastTouchEvents, constToString, dumpArgs) + "\n";
+    out += "mDevicesWithMixedToolType: " + dumpSet(mDevicesWithMixedToolType) + "\n";
+    out += "mCanceledDevices: " + dumpSet(mCanceledDevices) + "\n";
+    return out;
+}
+
+} // namespace android
diff --git a/services/inputflinger/PreferStylusOverTouchBlocker.h b/services/inputflinger/PreferStylusOverTouchBlocker.h
new file mode 100644
index 0000000..716dc4d
--- /dev/null
+++ b/services/inputflinger/PreferStylusOverTouchBlocker.h
@@ -0,0 +1,65 @@
+/*
+ * 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 <optional>
+#include <set>
+#include "InputListener.h"
+
+namespace android {
+
+/**
+ * When stylus is down, all touch is ignored.
+ * TODO(b/210159205): delete this when simultaneous stylus and touch is supported
+ */
+class PreferStylusOverTouchBlocker {
+public:
+    /**
+     * Process the provided event and emit 0 or more events that should be used instead of it.
+     * In the majority of cases, the returned result will just be the provided args (array with
+     * only 1 element), unmodified.
+     *
+     * If the gesture should be blocked, the returned result may be:
+     *
+     * a) An empty array, if the current event should just be ignored completely
+     * b) An array of N elements, containing N-1 events with ACTION_CANCEL and the current event.
+     *
+     * The returned result is intended to be reinjected into the original event stream in
+     * replacement of the incoming event.
+     */
+    std::vector<NotifyMotionArgs> processMotion(const NotifyMotionArgs& args);
+    std::string dump() const;
+
+    void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices);
+
+    void notifyDeviceReset(const NotifyDeviceResetArgs& args);
+
+private:
+    // Stores the device id's of styli that are currently down.
+    std::set<int32_t /*deviceId*/> mActiveStyli;
+    // For each device, store the last touch event as long as the touch is down. Upon liftoff,
+    // the entry is erased.
+    std::map<int32_t /*deviceId*/, NotifyMotionArgs> mLastTouchEvents;
+    // Device ids of devices for which the current touch gesture is canceled.
+    std::set<int32_t /*deviceId*/> mCanceledDevices;
+
+    // Device ids of input devices where we encountered simultaneous touch and stylus
+    // events. For these devices, we don't do any event processing (nothing is blocked or altered).
+    std::set<int32_t /*deviceId*/> mDevicesWithMixedToolType;
+};
+
+} // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/UnwantedInteractionBlocker.cpp b/services/inputflinger/UnwantedInteractionBlocker.cpp
index 64dbb8c..b69e16a 100644
--- a/services/inputflinger/UnwantedInteractionBlocker.cpp
+++ b/services/inputflinger/UnwantedInteractionBlocker.cpp
@@ -44,7 +44,8 @@
 }
 
 static bool isFromTouchscreen(int32_t source) {
-    return isFromSource(source, AINPUT_SOURCE_TOUCHSCREEN);
+    return isFromSource(source, AINPUT_SOURCE_TOUCHSCREEN) &&
+            !isFromSource(source, AINPUT_SOURCE_STYLUS);
 }
 
 static ::base::TimeTicks toChromeTimestamp(nsecs_t eventTime) {
@@ -367,6 +368,14 @@
 }
 
 void UnwantedInteractionBlocker::notifyMotion(const NotifyMotionArgs* args) {
+    const std::vector<NotifyMotionArgs> processedArgs =
+            mPreferStylusOverTouchBlocker.processMotion(*args);
+    for (const NotifyMotionArgs& loopArgs : processedArgs) {
+        notifyMotionInner(&loopArgs);
+    }
+}
+
+void UnwantedInteractionBlocker::notifyMotionInner(const NotifyMotionArgs* args) {
     auto it = mPalmRejectors.find(args->deviceId);
     const bool sendToPalmRejector = it != mPalmRejectors.end() && isFromTouchscreen(args->source);
     if (!sendToPalmRejector) {
@@ -400,6 +409,7 @@
         mPalmRejectors.emplace(args->deviceId, info);
     }
     mListener.notifyDeviceReset(args);
+    mPreferStylusOverTouchBlocker.notifyDeviceReset(*args);
 }
 
 void UnwantedInteractionBlocker::notifyPointerCaptureChanged(
@@ -436,10 +446,13 @@
         auto const& [deviceId, _] = item;
         return devicesToKeep.find(deviceId) == devicesToKeep.end();
     });
+    mPreferStylusOverTouchBlocker.notifyInputDevicesChanged(inputDevices);
 }
 
 void UnwantedInteractionBlocker::dump(std::string& dump) {
     dump += "UnwantedInteractionBlocker:\n";
+    dump += "  mPreferStylusOverTouchBlocker:\n";
+    dump += addPrefix(mPreferStylusOverTouchBlocker.dump(), "    ");
     dump += StringPrintf("  mEnablePalmRejection: %s\n", toString(mEnablePalmRejection));
     dump += StringPrintf("  isPalmRejectionEnabled (flag value): %s\n",
                          toString(isPalmRejectionEnabled()));
diff --git a/services/inputflinger/UnwantedInteractionBlocker.h b/services/inputflinger/UnwantedInteractionBlocker.h
index 14068fd..8a1cd72 100644
--- a/services/inputflinger/UnwantedInteractionBlocker.h
+++ b/services/inputflinger/UnwantedInteractionBlocker.h
@@ -23,6 +23,8 @@
 #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"
 
+#include "PreferStylusOverTouchBlocker.h"
+
 namespace android {
 
 // --- Functions for manipulation of event streams
@@ -88,9 +90,14 @@
     InputListenerInterface& mListener;
     const bool mEnablePalmRejection;
 
+    // When stylus is down, ignore touch
+    PreferStylusOverTouchBlocker mPreferStylusOverTouchBlocker;
+
     // Detect and reject unwanted palms on screen
     // Use a separate palm rejector for every touch device.
     std::map<int32_t /*deviceId*/, PalmRejector> mPalmRejectors;
+    // TODO(b/210159205): delete this when simultaneous stylus and touch is supported
+    void notifyMotionInner(const NotifyMotionArgs* args);
 };
 
 class SlotState {
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index 9691ad8..32eec29 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -263,15 +263,15 @@
 static void benchmarkNotifyMotion(benchmark::State& state) {
     // Create dispatcher
     sp<FakeInputDispatcherPolicy> fakePolicy = new FakeInputDispatcherPolicy();
-    std::unique_ptr<InputDispatcher> dispatcher = std::make_unique<InputDispatcher>(fakePolicy);
-    dispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
-    dispatcher->start();
+    InputDispatcher dispatcher(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}}});
+    dispatcher.setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
 
     NotifyMotionArgs motionArgs = generateMotionArgs();
 
@@ -280,55 +280,79 @@
         motionArgs.action = AMOTION_EVENT_ACTION_DOWN;
         motionArgs.downTime = now();
         motionArgs.eventTime = motionArgs.downTime;
-        dispatcher->notifyMotion(&motionArgs);
+        dispatcher.notifyMotion(&motionArgs);
 
         // Send ACTION_UP
         motionArgs.action = AMOTION_EVENT_ACTION_UP;
         motionArgs.eventTime = now();
-        dispatcher->notifyMotion(&motionArgs);
+        dispatcher.notifyMotion(&motionArgs);
 
         window->consumeEvent();
         window->consumeEvent();
     }
 
-    dispatcher->stop();
+    dispatcher.stop();
 }
 
 static void benchmarkInjectMotion(benchmark::State& state) {
     // Create dispatcher
     sp<FakeInputDispatcherPolicy> fakePolicy = new FakeInputDispatcherPolicy();
-    std::unique_ptr<InputDispatcher> dispatcher = std::make_unique<InputDispatcher>(fakePolicy);
-    dispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
-    dispatcher->start();
+    InputDispatcher dispatcher(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}}});
+    dispatcher.setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
 
     for (auto _ : state) {
         MotionEvent event = generateMotionEvent();
         // Send ACTION_DOWN
-        dispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
-                                     InputEventInjectionSync::NONE, INJECT_EVENT_TIMEOUT,
-                                     POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
+        dispatcher.injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
+                                    InputEventInjectionSync::NONE, INJECT_EVENT_TIMEOUT,
+                                    POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
 
         // Send ACTION_UP
         event.setAction(AMOTION_EVENT_ACTION_UP);
-        dispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
-                                     InputEventInjectionSync::NONE, INJECT_EVENT_TIMEOUT,
-                                     POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
+        dispatcher.injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
+                                    InputEventInjectionSync::NONE, INJECT_EVENT_TIMEOUT,
+                                    POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
 
         window->consumeEvent();
         window->consumeEvent();
     }
 
-    dispatcher->stop();
+    dispatcher.stop();
+}
+
+static void benchmarkOnWindowInfosChanged(benchmark::State& state) {
+    // Create dispatcher
+    sp<FakeInputDispatcherPolicy> fakePolicy = new FakeInputDispatcherPolicy();
+    InputDispatcher dispatcher(fakePolicy);
+    dispatcher.setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
+    dispatcher.start();
+
+    // Create a window
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = new FakeWindowHandle(application, dispatcher, "Fake Window");
+
+    std::vector<gui::WindowInfo> windowInfos{*window->getInfo()};
+    gui::DisplayInfo info;
+    info.displayId = window->getInfo()->displayId;
+    std::vector<gui::DisplayInfo> displayInfos{info};
+
+    for (auto _ : state) {
+        dispatcher.onWindowInfosChanged(windowInfos, displayInfos);
+        dispatcher.onWindowInfosChanged({} /*windowInfos*/, {} /*displayInfos*/);
+    }
+    dispatcher.stop();
 }
 
 BENCHMARK(benchmarkNotifyMotion);
 BENCHMARK(benchmarkInjectMotion);
+BENCHMARK(benchmarkOnWindowInfosChanged);
 
 } // namespace android::inputdispatcher
 
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index c8a3ccf..1cc4589 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -567,6 +567,17 @@
     return !info.inputConfig.test(gui::WindowInfo::InputConfig::NOT_TOUCHABLE) && !info.isSpy();
 }
 
+bool isWindowOwnedBy(const sp<WindowInfoHandle>& windowHandle, int32_t pid, int32_t uid) {
+    if (windowHandle == nullptr) {
+        return false;
+    }
+    const WindowInfo* windowInfo = windowHandle->getInfo();
+    if (pid == windowInfo->ownerPid && uid == windowInfo->ownerUid) {
+        return true;
+    }
+    return false;
+}
+
 } // namespace
 
 // --- InputDispatcher ---
@@ -2350,8 +2361,8 @@
                                         *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);
+        ALOGI("Dropping event because there is no touched window on display %d to receive it: %s",
+              displayId, entry.getDescription().c_str());
         injectionResult = InputEventInjectionResult::FAILED;
         goto Failed;
     }
@@ -4990,21 +5001,11 @@
                   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) {
+            if (!focusedWindowIsOwnedByLocked(pid, uid) &&
+                !recentWindowsAreOwnedByLocked(pid, uid)) {
+                ALOGD("Touch mode switch rejected, caller (pid=%d, uid=%d) doesn't own the focused "
+                      "window nor none of the previously interacted window",
+                      pid, uid);
                 return false;
             }
         }
@@ -5022,6 +5023,24 @@
     return true;
 }
 
+bool InputDispatcher::focusedWindowIsOwnedByLocked(int32_t pid, int32_t uid) {
+    const sp<IBinder> focusedToken = mFocusResolver.getFocusedWindowToken(mFocusedDisplayId);
+    if (focusedToken == nullptr) {
+        return false;
+    }
+    sp<WindowInfoHandle> windowHandle = getWindowHandleLocked(focusedToken);
+    return isWindowOwnedBy(windowHandle, pid, uid);
+}
+
+bool InputDispatcher::recentWindowsAreOwnedByLocked(int32_t pid, int32_t uid) {
+    return std::find_if(mInteractionConnectionTokens.begin(), mInteractionConnectionTokens.end(),
+                        [&](const sp<IBinder>& connectionToken) REQUIRES(mLock) {
+                            const sp<WindowInfoHandle> windowHandle =
+                                    getWindowHandleLocked(connectionToken);
+                            return isWindowOwnedBy(windowHandle, pid, uid);
+                        }) != mInteractionConnectionTokens.end();
+}
+
 void InputDispatcher::setMaximumObscuringOpacityForTouch(float opacity) {
     if (opacity < 0 || opacity > 1) {
         LOG_ALWAYS_FATAL("Maximum obscuring opacity for touch should be >= 0 and <= 1");
@@ -5121,28 +5140,54 @@
     return true;
 }
 
+/**
+ * Get the touched foreground window on the given display.
+ * Return null if there are no windows touched on that display, or if more than one foreground
+ * window is being touched.
+ */
+sp<WindowInfoHandle> InputDispatcher::findTouchedForegroundWindowLocked(int32_t displayId) const {
+    auto stateIt = mTouchStatesByDisplay.find(displayId);
+    if (stateIt == mTouchStatesByDisplay.end()) {
+        ALOGI("No touch state on display %" PRId32, displayId);
+        return nullptr;
+    }
+
+    const TouchState& state = stateIt->second;
+    sp<WindowInfoHandle> touchedForegroundWindow;
+    // If multiple foreground windows are touched, return nullptr
+    for (const TouchedWindow& window : state.windows) {
+        if (window.targetFlags & InputTarget::FLAG_FOREGROUND) {
+            if (touchedForegroundWindow != nullptr) {
+                ALOGI("Two or more foreground windows: %s and %s",
+                      touchedForegroundWindow->getName().c_str(),
+                      window.windowHandle->getName().c_str());
+                return nullptr;
+            }
+            touchedForegroundWindow = window.windowHandle;
+        }
+    }
+    return touchedForegroundWindow;
+}
+
 // Binder call
-bool InputDispatcher::transferTouch(const sp<IBinder>& destChannelToken) {
+bool InputDispatcher::transferTouch(const sp<IBinder>& destChannelToken, int32_t displayId) {
     sp<IBinder> fromToken;
     { // acquire lock
         std::scoped_lock _l(mLock);
-
-        auto it = std::find_if(mTouchStatesByDisplay.begin(), mTouchStatesByDisplay.end(),
-                               [](const auto& pair) { return pair.second.windows.size() == 1; });
-        if (it == mTouchStatesByDisplay.end()) {
-            ALOGW("Cannot transfer touch state because there is no exact window being touched");
-            return false;
-        }
-        const int32_t displayId = it->first;
         sp<WindowInfoHandle> toWindowHandle = getWindowHandleLocked(destChannelToken, displayId);
         if (toWindowHandle == nullptr) {
-            ALOGW("Could not find window associated with token=%p", destChannelToken.get());
+            ALOGW("Could not find window associated with token=%p on display %" PRId32,
+                  destChannelToken.get(), displayId);
             return false;
         }
 
-        TouchState& state = it->second;
-        const TouchedWindow& touchedWindow = state.windows[0];
-        fromToken = touchedWindow.windowHandle->getToken();
+        sp<WindowInfoHandle> from = findTouchedForegroundWindowLocked(displayId);
+        if (from == nullptr) {
+            ALOGE("Could not find a source window in %s for %p", __func__, destChannelToken.get());
+            return false;
+        }
+
+        fromToken = from->getToken();
     } // release lock
 
     return transferTouchFocus(fromToken, destChannelToken);
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 3c79c98..f3dac19 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -125,7 +125,7 @@
 
     bool transferTouchFocus(const sp<IBinder>& fromToken, const sp<IBinder>& toToken,
                             bool isDragDrop = false) override;
-    bool transferTouch(const sp<IBinder>& destChannelToken) override;
+    bool transferTouch(const sp<IBinder>& destChannelToken, int32_t displayId) override;
 
     base::Result<std::unique_ptr<InputChannel>> createInputChannel(
             const std::string& name) override;
@@ -245,6 +245,9 @@
     std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAtLocked(
             int32_t displayId, int32_t x, int32_t y, bool isStylus) const REQUIRES(mLock);
 
+    sp<android::gui::WindowInfoHandle> findTouchedForegroundWindowLocked(int32_t displayId) const
+            REQUIRES(mLock);
+
     sp<Connection> getConnectionLocked(const sp<IBinder>& inputConnectionToken) const
             REQUIRES(mLock);
 
@@ -433,7 +436,8 @@
     // Dispatcher state at time of last ANR.
     std::string mLastAnrState GUARDED_BY(mLock);
 
-    // The connection tokens of the channels that the user last interacted, for debugging
+    // The connection tokens of the channels that the user last interacted (used for debugging and
+    // when switching touch mode state).
     std::unordered_set<sp<IBinder>, StrongPointerHash<IBinder>> mInteractionConnectionTokens
             GUARDED_BY(mLock);
     void updateInteractionTokensLocked(const EventEntry& entry,
@@ -677,6 +681,10 @@
     void traceOutboundQueueLength(const Connection& connection);
     void traceWaitQueueLength(const Connection& connection);
 
+    // Check window ownership
+    bool focusedWindowIsOwnedByLocked(int32_t pid, int32_t uid) REQUIRES(mLock);
+    bool recentWindowsAreOwnedByLocked(int32_t pid, int32_t uid) REQUIRES(mLock);
+
     sp<InputReporterInterface> mReporter;
 };
 
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index 67e1b6f..d7bc5fb 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -155,7 +155,7 @@
      *
      * Return true on success, false if there was no on-going touch.
      */
-    virtual bool transferTouch(const sp<IBinder>& destChannelToken) = 0;
+    virtual bool transferTouch(const sp<IBinder>& destChannelToken, int32_t displayId) = 0;
 
     /**
      * Sets focus on the specified window.
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index 8bd3899..d6a6bd2 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -60,10 +60,11 @@
 #define INDENT3 "      "
 
 using android::base::StringPrintf;
-using namespace android::flag_operators;
 
 namespace android {
 
+using namespace ftl::flag_operators;
+
 static const char* DEVICE_INPUT_PATH = "/dev/input";
 // v4l2 devices go directly into /dev
 static const char* DEVICE_PATH = "/dev";
@@ -302,7 +303,8 @@
 
 // --- Global Functions ---
 
-Flags<InputDeviceClass> getAbsAxisUsage(int32_t axis, Flags<InputDeviceClass> deviceClasses) {
+ftl::Flags<InputDeviceClass> getAbsAxisUsage(int32_t axis,
+                                             ftl::Flags<InputDeviceClass> deviceClasses) {
     // Touch devices get dibs on touch-related axes.
     if (deviceClasses.test(InputDeviceClass::TOUCH)) {
         switch (axis) {
@@ -765,10 +767,10 @@
     return device != nullptr ? device->identifier : InputDeviceIdentifier();
 }
 
-Flags<InputDeviceClass> EventHub::getDeviceClasses(int32_t deviceId) const {
+ftl::Flags<InputDeviceClass> EventHub::getDeviceClasses(int32_t deviceId) const {
     std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
-    return device != nullptr ? device->classes : Flags<InputDeviceClass>(0);
+    return device != nullptr ? device->classes : ftl::Flags<InputDeviceClass>(0);
 }
 
 int32_t EventHub::getDeviceControllerNumber(int32_t deviceId) const {
@@ -1909,7 +1911,7 @@
 }
 
 void EventHub::reportDeviceAddedForStatisticsLocked(const InputDeviceIdentifier& identifier,
-                                                    Flags<InputDeviceClass> classes) {
+                                                    ftl::Flags<InputDeviceClass> classes) {
     SHA256_CTX ctx;
     SHA256_Init(&ctx);
     SHA256_Update(&ctx, reinterpret_cast<const uint8_t*>(identifier.uniqueId.c_str()),
@@ -2191,7 +2193,7 @@
     }
 
     // If the device isn't recognized as something we handle, don't monitor it.
-    if (device->classes == Flags<InputDeviceClass>(0)) {
+    if (device->classes == ftl::Flags<InputDeviceClass>(0)) {
         ALOGV("Dropping device: id=%d, path='%s', name='%s'", deviceId, devicePath.c_str(),
               device->identifier.name.c_str());
         return;
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index a050963..ba5083b 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -18,9 +18,10 @@
 
 #include "InputDevice.h"
 
-#include <ftl/Flags.h>
 #include <algorithm>
 
+#include <ftl/flags.h>
+
 #include "CursorInputMapper.h"
 #include "ExternalStylusInputMapper.h"
 #include "InputReaderContext.h"
@@ -145,7 +146,7 @@
         return;
     }
     std::unique_ptr<InputDeviceContext> contextPtr(new InputDeviceContext(*this, eventHubId));
-    Flags<InputDeviceClass> classes = contextPtr->getDeviceClasses();
+    ftl::Flags<InputDeviceClass> classes = contextPtr->getDeviceClasses();
     std::vector<std::unique_ptr<InputMapper>> mappers;
 
     // Check if we should skip population
@@ -236,7 +237,7 @@
 void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config,
                             uint32_t changes) {
     mSources = 0;
-    mClasses = Flags<InputDeviceClass>(0);
+    mClasses = ftl::Flags<InputDeviceClass>(0);
     mControllerNumber = 0;
 
     for_each_subdevice([this](InputDeviceContext& context) {
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 18e912d..130c556 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -19,13 +19,12 @@
 
 #include <bitset>
 #include <climits>
+#include <filesystem>
 #include <unordered_map>
 #include <vector>
 
-#include <ftl/Flags.h>
-#include <filesystem>
-
 #include <batteryservice/BatteryService.h>
+#include <ftl/flags.h>
 #include <input/Input.h>
 #include <input/InputDevice.h>
 #include <input/KeyCharacterMap.h>
@@ -189,7 +188,7 @@
     int32_t id;
     std::string name;
     std::optional<int32_t> maxBrightness;
-    Flags<InputLightClass> flags;
+    ftl::Flags<InputLightClass> flags;
     std::array<int32_t, COLOR_NUM> rgbIndex;
     std::filesystem::path path;
 };
@@ -198,7 +197,7 @@
 struct RawBatteryInfo {
     int32_t id;
     std::string name;
-    Flags<InputBatteryClass> flags;
+    ftl::Flags<InputBatteryClass> flags;
     std::filesystem::path path;
 };
 
@@ -206,7 +205,8 @@
  * Gets the class that owns an axis, in cases where multiple classes might claim
  * the same axis for different purposes.
  */
-extern Flags<InputDeviceClass> getAbsAxisUsage(int32_t axis, Flags<InputDeviceClass> deviceClasses);
+extern ftl::Flags<InputDeviceClass> getAbsAxisUsage(int32_t axis,
+                                                    ftl::Flags<InputDeviceClass> deviceClasses);
 
 /*
  * Grand Central Station for events.
@@ -239,7 +239,7 @@
         FIRST_SYNTHETIC_EVENT = DEVICE_ADDED,
     };
 
-    virtual Flags<InputDeviceClass> getDeviceClasses(int32_t deviceId) const = 0;
+    virtual ftl::Flags<InputDeviceClass> getDeviceClasses(int32_t deviceId) const = 0;
 
     virtual InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const = 0;
 
@@ -436,7 +436,7 @@
 public:
     EventHub();
 
-    Flags<InputDeviceClass> getDeviceClasses(int32_t deviceId) const override final;
+    ftl::Flags<InputDeviceClass> getDeviceClasses(int32_t deviceId) const override final;
 
     InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const override final;
 
@@ -559,7 +559,7 @@
 
         std::unique_ptr<TouchVideoDevice> videoDevice;
 
-        Flags<InputDeviceClass> classes;
+        ftl::Flags<InputDeviceClass> classes;
 
         BitArray<KEY_MAX> keyBitmask;
         BitArray<KEY_MAX> keyState;
@@ -662,7 +662,7 @@
     int32_t getNextControllerNumberLocked(const std::string& name) REQUIRES(mLock);
     void releaseControllerNumberLocked(int32_t num) REQUIRES(mLock);
     void reportDeviceAddedForStatisticsLocked(const InputDeviceIdentifier& identifier,
-                                              Flags<InputDeviceClass> classes) REQUIRES(mLock);
+                                              ftl::Flags<InputDeviceClass> classes) REQUIRES(mLock);
 
     const std::unordered_map<int32_t, RawBatteryInfo>& getBatteryInfoLocked(int32_t deviceId) const
             REQUIRES(mLock);
@@ -725,6 +725,6 @@
     bool mPendingINotify;
 };
 
-}; // namespace android
+} // namespace android
 
 #endif // _RUNTIME_EVENT_HUB_H
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 694daa9..728020e 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -17,12 +17,12 @@
 #ifndef _UI_INPUTREADER_INPUT_DEVICE_H
 #define _UI_INPUTREADER_INPUT_DEVICE_H
 
-#include <ftl/Flags.h>
+#include <ftl/flags.h>
 #include <input/DisplayViewport.h>
 #include <input/InputDevice.h>
 #include <input/PropertyMap.h>
-#include <stdint.h>
 
+#include <cstdint>
 #include <optional>
 #include <unordered_map>
 #include <vector>
@@ -53,7 +53,7 @@
     inline int32_t getGeneration() const { return mGeneration; }
     inline const std::string getName() const { return mIdentifier.name; }
     inline const std::string getDescriptor() { return mIdentifier.descriptor; }
-    inline Flags<InputDeviceClass> getClasses() const { return mClasses; }
+    inline ftl::Flags<InputDeviceClass> getClasses() const { return mClasses; }
     inline uint32_t getSources() const { return mSources; }
     inline bool hasEventHubDevices() const { return !mDevices.empty(); }
 
@@ -160,7 +160,7 @@
     int32_t mControllerNumber;
     InputDeviceIdentifier mIdentifier;
     std::string mAlias;
-    Flags<InputDeviceClass> mClasses;
+    ftl::Flags<InputDeviceClass> mClasses;
 
     // map from eventHubId to device context and mappers
     using MapperVector = std::vector<std::unique_ptr<InputMapper>>;
@@ -250,7 +250,7 @@
     inline int32_t getId() { return mDeviceId; }
     inline int32_t getEventHubId() { return mId; }
 
-    inline Flags<InputDeviceClass> getDeviceClasses() const {
+    inline ftl::Flags<InputDeviceClass> getDeviceClasses() const {
         return mEventHub->getDeviceClasses(mId);
     }
     inline InputDeviceIdentifier getDeviceIdentifier() const {
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index 1d63c0e..2ac8178 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -428,6 +428,8 @@
 }
 
 void KeyboardInputMapper::updateLedState(bool reset) {
+    // Clear the local led state then union the global led state.
+    mMetaState &= ~(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON | AMETA_SCROLL_LOCK_ON);
     mMetaState |= getContext()->getLedMetaState();
 
     constexpr int32_t META_NUM = 3;
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 9d200bd..76a7c19 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -47,6 +47,7 @@
         "InputReader_test.cpp",
         "InputFlingerService_test.cpp",
         "LatencyTracker_test.cpp",
+        "PreferStylusOverTouch_test.cpp",
         "TestInputListener.cpp",
         "UinputDevice.cpp",
         "UnwantedInteractionBlocker_test.cpp",
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index bf58705..a167271 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -39,10 +39,11 @@
 using android::gui::WindowInfoHandle;
 using android::os::InputEventInjectionResult;
 using android::os::InputEventInjectionSync;
-using namespace android::flag_operators;
 
 namespace android::inputdispatcher {
 
+using namespace ftl::flag_operators;
+
 // An arbitrary time value.
 static const nsecs_t ARBITRARY_TIME = 1234;
 
@@ -55,6 +56,8 @@
 
 static constexpr int32_t POINTER_1_DOWN =
         AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+static constexpr int32_t POINTER_2_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (2 << 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);
 
@@ -2244,6 +2247,53 @@
 }
 
 /**
+ * This test documents the behavior of WATCH_OUTSIDE_TOUCH. The window will get ACTION_OUTSIDE when
+ * a another pointer causes ACTION_DOWN to be sent to another window for the first time. Only one
+ * ACTION_OUTSIDE event is sent per gesture.
+ */
+TEST_F(InputDispatcherTest, ActionOutsideSentOnlyWhenAWindowIsTouched) {
+    // There are three windows that do not overlap. `window` wants to WATCH_OUTSIDE_TOUCH.
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT);
+    window->setWatchOutsideTouch(true);
+    window->setFrame(Rect{0, 0, 100, 100});
+    sp<FakeWindowHandle> secondWindow =
+            new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT);
+    secondWindow->setFrame(Rect{100, 100, 200, 200});
+    sp<FakeWindowHandle> thirdWindow =
+            new FakeWindowHandle(application, mDispatcher, "Third Window", ADISPLAY_ID_DEFAULT);
+    thirdWindow->setFrame(Rect{200, 200, 300, 300});
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, secondWindow, thirdWindow}}});
+
+    // First pointer lands outside all windows. `window` does not get ACTION_OUTSIDE.
+    NotifyMotionArgs motionArgs =
+            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ADISPLAY_ID_DEFAULT, {PointF{-10, -10}});
+    mDispatcher->notifyMotion(&motionArgs);
+    window->assertNoEvents();
+    secondWindow->assertNoEvents();
+
+    // The second pointer lands inside `secondWindow`, which should receive a DOWN event.
+    // Now, `window` should get ACTION_OUTSIDE.
+    motionArgs = generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                    {PointF{-10, -10}, PointF{105, 105}});
+    mDispatcher->notifyMotion(&motionArgs);
+    window->consumeMotionOutside();
+    secondWindow->consumeMotionDown();
+    thirdWindow->assertNoEvents();
+
+    // The third pointer lands inside `thirdWindow`, which should receive a DOWN event. There is
+    // no ACTION_OUTSIDE sent to `window` because one has already been sent for this gesture.
+    motionArgs = generateMotionArgs(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                    {PointF{-10, -10}, PointF{105, 105}, PointF{205, 205}});
+    mDispatcher->notifyMotion(&motionArgs);
+    window->assertNoEvents();
+    secondWindow->consumeMotionMove();
+    thirdWindow->consumeMotionDown();
+}
+
+/**
  * Ensure the correct coordinate spaces are used by InputDispatcher.
  *
  * InputDispatcher works in the display space, so its coordinate system is relative to the display
@@ -2426,6 +2476,63 @@
     secondWindow->consumeMotionUp();
 }
 
+/**
+ * When 'transferTouch' API is invoked, dispatcher needs to find the "best" window to take touch
+ * from. When we have spy windows, there are several windows to choose from: either spy, or the
+ * 'real' (non-spy) window. Always prefer the 'real' window because that's what would be most
+ * natural to the user.
+ * In this test, we are sending a pointer to both spy window and first window. We then try to
+ * transfer touch to the second window. The dispatcher should identify the first window as the
+ * one that should lose the gesture, and therefore the action should be to move the gesture from
+ * the first window to the second.
+ * The main goal here is to test the behaviour of 'transferTouch' API, but it's still valid to test
+ * the other API, as well.
+ */
+TEST_P(TransferTouchFixture, TransferTouch_MultipleWindowsWithSpy) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    // Create a couple of windows + a spy window
+    sp<FakeWindowHandle> spyWindow =
+            new FakeWindowHandle(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setSpy(true);
+    sp<FakeWindowHandle> firstWindow =
+            new FakeWindowHandle(application, mDispatcher, "First", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> secondWindow =
+            new FakeWindowHandle(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
+
+    // Add the windows to the dispatcher
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, firstWindow, secondWindow}}});
+
+    // Send down to the first window
+    NotifyMotionArgs downMotionArgs =
+            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyMotion(&downMotionArgs);
+    // Only the first window and spy should get the down event
+    spyWindow->consumeMotionDown();
+    firstWindow->consumeMotionDown();
+
+    // Transfer touch to the second window. Non-spy window should be preferred over the spy window
+    // if f === 'transferTouch'.
+    TransferFunction f = GetParam();
+    const bool success = f(mDispatcher, firstWindow->getToken(), secondWindow->getToken());
+    ASSERT_TRUE(success);
+    // The first window gets cancel and the second gets down
+    firstWindow->consumeMotionCancel();
+    secondWindow->consumeMotionDown();
+
+    // Send up event to the second window
+    NotifyMotionArgs upMotionArgs =
+            generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                               ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyMotion(&upMotionArgs);
+    // The first  window gets no events and the second+spy get up
+    firstWindow->assertNoEvents();
+    spyWindow->consumeMotionUp();
+    secondWindow->consumeMotionUp();
+}
+
 TEST_P(TransferTouchFixture, TransferTouch_TwoPointersNonSplitTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
@@ -2495,7 +2602,8 @@
                          ::testing::Values(
                                  [&](const std::unique_ptr<InputDispatcher>& dispatcher,
                                      sp<IBinder> /*ignored*/, sp<IBinder> destChannelToken) {
-                                     return dispatcher->transferTouch(destChannelToken);
+                                     return dispatcher->transferTouch(destChannelToken,
+                                                                      ADISPLAY_ID_DEFAULT);
                                  },
                                  [&](const std::unique_ptr<InputDispatcher>& dispatcher,
                                      sp<IBinder> from, sp<IBinder> to) {
@@ -2603,7 +2711,8 @@
     secondWindow->consumeMotionDown();
 
     // Transfer touch focus to the second window
-    const bool transferred = mDispatcher->transferTouch(secondWindow->getToken());
+    const bool transferred =
+            mDispatcher->transferTouch(secondWindow->getToken(), ADISPLAY_ID_DEFAULT);
     // The 'transferTouch' call should not succeed, because there are 2 touched windows
     ASSERT_FALSE(transferred);
     firstWindow->assertNoEvents();
@@ -2727,7 +2836,7 @@
     firstWindowInPrimary->consumeMotionDown(SECOND_DISPLAY_ID);
 
     // Transfer touch focus
-    ASSERT_TRUE(mDispatcher->transferTouch(secondWindowInSecondary->getToken()));
+    ASSERT_TRUE(mDispatcher->transferTouch(secondWindowInSecondary->getToken(), SECOND_DISPLAY_ID));
 
     // The first window gets cancel.
     firstWindowInPrimary->consumeMotionCancel(SECOND_DISPLAY_ID);
@@ -6335,8 +6444,11 @@
         mWindow->consumeFocusEvent(true);
 
         // Set initial touch mode to InputDispatcher::kDefaultInTouchMode.
-        mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, INJECTOR_PID,
-                                    INJECTOR_UID, /* hasPermission */ true);
+        if (mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, INJECTOR_PID,
+                                        INJECTOR_UID, /* hasPermission */ true)) {
+            mWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode);
+            mSecondWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode);
+        }
     }
 
     void changeAndVerifyTouchMode(bool inTouchMode, int32_t pid, int32_t uid, bool hasPermission) {
@@ -6381,6 +6493,23 @@
     mSecondWindow->assertNoEvents();
 }
 
+TEST_F(InputDispatcherTouchModeChangedTests, CanChangeTouchModeWhenOwningLastInteractedWindow) {
+    // Interact with the window first.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher, ADISPLAY_ID_DEFAULT))
+            << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
+    mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+
+    // Then remove focus.
+    mWindow->setFocusable(false);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+
+    // Assert that caller can switch touch mode by owning one of the last interacted window.
+    const WindowInfo& windowInfo = *mWindow->getInfo();
+    ASSERT_TRUE(mDispatcher->setInTouchMode(!InputDispatcher::kDefaultInTouchMode,
+                                            windowInfo.ownerPid, windowInfo.ownerUid,
+                                            /* hasPermission= */ false));
+}
+
 class InputDispatcherSpyWindowTest : public InputDispatcherTest {
 public:
     sp<FakeWindowHandle> createSpy() {
@@ -6665,9 +6794,7 @@
 
     // 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)
+            MotionEventBuilder(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 9f33d23..bda7755 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+#include <cinttypes>
+#include <memory>
+
 #include <CursorInputMapper.h>
 #include <InputDevice.h>
 #include <InputMapper.h>
@@ -34,18 +37,15 @@
 #include <android-base/thread_annotations.h>
 #include <gtest/gtest.h>
 #include <gui/constants.h>
-#include <inttypes.h>
-#include <math.h>
 
-#include <memory>
-#include <regex>
 #include "input/DisplayViewport.h"
 #include "input/Input.h"
 
 namespace android {
 
+using namespace ftl::flag_operators;
+
 using std::chrono_literals::operator""ms;
-using namespace android::flag_operators;
 
 // Timeout for waiting for an expected event
 static constexpr std::chrono::duration WAIT_TIMEOUT = 100ms;
@@ -429,7 +429,7 @@
 
     struct Device {
         InputDeviceIdentifier identifier;
-        Flags<InputDeviceClass> classes;
+        ftl::Flags<InputDeviceClass> classes;
         PropertyMap configuration;
         KeyedVector<int, RawAbsoluteAxisInfo> absoluteAxes;
         KeyedVector<int, bool> relativeAxes;
@@ -457,7 +457,7 @@
             return OK;
         }
 
-        explicit Device(Flags<InputDeviceClass> classes) : classes(classes), enabled(true) {}
+        explicit Device(ftl::Flags<InputDeviceClass> classes) : classes(classes), enabled(true) {}
     };
 
     std::mutex mLock;
@@ -484,7 +484,8 @@
 
     FakeEventHub() { }
 
-    void addDevice(int32_t deviceId, const std::string& name, Flags<InputDeviceClass> classes) {
+    void addDevice(int32_t deviceId, const std::string& name,
+                   ftl::Flags<InputDeviceClass> classes) {
         Device* device = new Device(classes);
         device->identifier.name = name;
         mDevices.add(deviceId, device);
@@ -695,9 +696,9 @@
         return index >= 0 ? mDevices.valueAt(index) : nullptr;
     }
 
-    Flags<InputDeviceClass> getDeviceClasses(int32_t deviceId) const override {
+    ftl::Flags<InputDeviceClass> getDeviceClasses(int32_t deviceId) const override {
         Device* device = getDevice(deviceId);
-        return device ? device->classes : Flags<InputDeviceClass>(0);
+        return device ? device->classes : ftl::Flags<InputDeviceClass>(0);
     }
 
     InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const override {
@@ -1572,8 +1573,8 @@
         mFakePolicy.clear();
     }
 
-    void addDevice(int32_t eventHubId, const std::string& name, Flags<InputDeviceClass> classes,
-                   const PropertyMap* configuration) {
+    void addDevice(int32_t eventHubId, const std::string& name,
+                   ftl::Flags<InputDeviceClass> classes, const PropertyMap* configuration) {
         mFakeEventHub->addDevice(eventHubId, name, classes);
 
         if (configuration) {
@@ -1598,7 +1599,8 @@
 
     FakeInputMapper& addDeviceWithFakeInputMapper(int32_t deviceId, int32_t eventHubId,
                                                   const std::string& name,
-                                                  Flags<InputDeviceClass> classes, uint32_t sources,
+                                                  ftl::Flags<InputDeviceClass> classes,
+                                                  uint32_t sources,
                                                   const PropertyMap* configuration) {
         std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, name);
         FakeInputMapper& mapper = device->addMapper<FakeInputMapper>(eventHubId, sources);
@@ -1610,7 +1612,7 @@
 
 TEST_F(InputReaderTest, PolicyGetInputDevices) {
     ASSERT_NO_FATAL_FAILURE(addDevice(1, "keyboard", InputDeviceClass::KEYBOARD, nullptr));
-    ASSERT_NO_FATAL_FAILURE(addDevice(2, "ignored", Flags<InputDeviceClass>(0),
+    ASSERT_NO_FATAL_FAILURE(addDevice(2, "ignored", ftl::Flags<InputDeviceClass>(0),
                                       nullptr)); // no classes so device will be ignored
 
     // Should also have received a notification describing the new input devices.
@@ -1672,7 +1674,7 @@
 
 TEST_F(InputReaderTest, WhenEnabledChanges_SendsDeviceResetNotification) {
     constexpr int32_t deviceId = END_RESERVED_ID + 1000;
-    constexpr Flags<InputDeviceClass> deviceClass(InputDeviceClass::KEYBOARD);
+    constexpr ftl::Flags<InputDeviceClass> deviceClass(InputDeviceClass::KEYBOARD);
     constexpr int32_t eventHubId = 1;
     std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
     // Must add at least one mapper or the device will be ignored!
@@ -1709,7 +1711,7 @@
 
 TEST_F(InputReaderTest, GetKeyCodeState_ForwardsRequestsToMappers) {
     constexpr int32_t deviceId = END_RESERVED_ID + 1000;
-    constexpr Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
+    constexpr ftl::Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
     constexpr int32_t eventHubId = 1;
     FakeInputMapper& mapper =
             addDeviceWithFakeInputMapper(deviceId, eventHubId, "fake", deviceClass,
@@ -1773,7 +1775,7 @@
 
 TEST_F(InputReaderTest, GetScanCodeState_ForwardsRequestsToMappers) {
     constexpr int32_t deviceId = END_RESERVED_ID + 1000;
-    constexpr Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
+    constexpr ftl::Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
     constexpr int32_t eventHubId = 1;
     FakeInputMapper& mapper =
             addDeviceWithFakeInputMapper(deviceId, eventHubId, "fake", deviceClass,
@@ -1806,7 +1808,7 @@
 
 TEST_F(InputReaderTest, GetSwitchState_ForwardsRequestsToMappers) {
     constexpr int32_t deviceId = END_RESERVED_ID + 1000;
-    constexpr Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
+    constexpr ftl::Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
     constexpr int32_t eventHubId = 1;
     FakeInputMapper& mapper =
             addDeviceWithFakeInputMapper(deviceId, eventHubId, "fake", deviceClass,
@@ -1839,7 +1841,7 @@
 
 TEST_F(InputReaderTest, MarkSupportedKeyCodes_ForwardsRequestsToMappers) {
     constexpr int32_t deviceId = END_RESERVED_ID + 1000;
-    constexpr Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
+    constexpr ftl::Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
     constexpr int32_t eventHubId = 1;
     FakeInputMapper& mapper =
             addDeviceWithFakeInputMapper(deviceId, eventHubId, "fake", deviceClass,
@@ -1891,7 +1893,7 @@
 
 TEST_F(InputReaderTest, LoopOnce_ForwardsRawEventsToMappers) {
     constexpr int32_t deviceId = END_RESERVED_ID + 1000;
-    constexpr Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
+    constexpr ftl::Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
     constexpr nsecs_t when = 0;
     constexpr int32_t eventHubId = 1;
     constexpr nsecs_t readTime = 2;
@@ -1915,7 +1917,7 @@
 
 TEST_F(InputReaderTest, DeviceReset_RandomId) {
     constexpr int32_t deviceId = END_RESERVED_ID + 1000;
-    constexpr Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
+    constexpr ftl::Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
     constexpr int32_t eventHubId = 1;
     std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
     // Must add at least one mapper or the device will be ignored!
@@ -1948,7 +1950,7 @@
 
 TEST_F(InputReaderTest, DeviceReset_GenerateIdWithInputReaderSource) {
     constexpr int32_t deviceId = 1;
-    constexpr Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
+    constexpr ftl::Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
     constexpr int32_t eventHubId = 1;
     std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
     // Must add at least one mapper or the device will be ignored!
@@ -1963,7 +1965,7 @@
 
 TEST_F(InputReaderTest, Device_CanDispatchToDisplay) {
     constexpr int32_t deviceId = END_RESERVED_ID + 1000;
-    constexpr Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
+    constexpr ftl::Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
     constexpr int32_t eventHubId = 1;
     const char* DEVICE_LOCATION = "USB1";
     std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION);
@@ -2008,7 +2010,7 @@
 
 TEST_F(InputReaderTest, WhenEnabledChanges_AllSubdevicesAreUpdated) {
     constexpr int32_t deviceId = END_RESERVED_ID + 1000;
-    constexpr Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
+    constexpr ftl::Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
     constexpr int32_t eventHubIds[2] = {END_RESERVED_ID, END_RESERVED_ID + 1};
     std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
     // Must add at least one mapper or the device will be ignored!
@@ -2049,7 +2051,7 @@
 
 TEST_F(InputReaderTest, GetKeyCodeState_ForwardsRequestsToSubdeviceMappers) {
     constexpr int32_t deviceId = END_RESERVED_ID + 1000;
-    constexpr Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
+    constexpr ftl::Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
     constexpr int32_t eventHubIds[2] = {END_RESERVED_ID, END_RESERVED_ID + 1};
     // Add two subdevices to device
     std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
@@ -2106,7 +2108,8 @@
 
 TEST_F(InputReaderTest, VibratorGetVibratorIds) {
     constexpr int32_t deviceId = END_RESERVED_ID + 1000;
-    Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD | InputDeviceClass::VIBRATOR;
+    ftl::Flags<InputDeviceClass> deviceClass =
+            InputDeviceClass::KEYBOARD | InputDeviceClass::VIBRATOR;
     constexpr int32_t eventHubId = 1;
     const char* DEVICE_LOCATION = "BLUETOOTH";
     std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION);
@@ -2166,7 +2169,8 @@
 
 TEST_F(InputReaderTest, BatteryGetCapacity) {
     constexpr int32_t deviceId = END_RESERVED_ID + 1000;
-    Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD | InputDeviceClass::BATTERY;
+    ftl::Flags<InputDeviceClass> deviceClass =
+            InputDeviceClass::KEYBOARD | InputDeviceClass::BATTERY;
     constexpr int32_t eventHubId = 1;
     const char* DEVICE_LOCATION = "BLUETOOTH";
     std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION);
@@ -2182,7 +2186,8 @@
 
 TEST_F(InputReaderTest, BatteryGetStatus) {
     constexpr int32_t deviceId = END_RESERVED_ID + 1000;
-    Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD | InputDeviceClass::BATTERY;
+    ftl::Flags<InputDeviceClass> deviceClass =
+            InputDeviceClass::KEYBOARD | InputDeviceClass::BATTERY;
     constexpr int32_t eventHubId = 1;
     const char* DEVICE_LOCATION = "BLUETOOTH";
     std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION);
@@ -2198,7 +2203,7 @@
 
 TEST_F(InputReaderTest, LightGetColor) {
     constexpr int32_t deviceId = END_RESERVED_ID + 1000;
-    Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD | InputDeviceClass::LIGHT;
+    ftl::Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD | InputDeviceClass::LIGHT;
     constexpr int32_t eventHubId = 1;
     const char* DEVICE_LOCATION = "BLUETOOTH";
     std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION);
@@ -2625,7 +2630,7 @@
     static const int32_t DEVICE_ID;
     static const int32_t DEVICE_GENERATION;
     static const int32_t DEVICE_CONTROLLER_NUMBER;
-    static const Flags<InputDeviceClass> DEVICE_CLASSES;
+    static const ftl::Flags<InputDeviceClass> DEVICE_CLASSES;
     static const int32_t EVENTHUB_ID;
 
     std::shared_ptr<FakeEventHub> mFakeEventHub;
@@ -2646,7 +2651,7 @@
         mDevice = std::make_shared<InputDevice>(mReader->getContext(), DEVICE_ID, DEVICE_GENERATION,
                                                 identifier);
         mReader->pushNextDevice(mDevice);
-        mFakeEventHub->addDevice(EVENTHUB_ID, DEVICE_NAME, Flags<InputDeviceClass>(0));
+        mFakeEventHub->addDevice(EVENTHUB_ID, DEVICE_NAME, ftl::Flags<InputDeviceClass>(0));
         mReader->loopOnce();
     }
 
@@ -2661,14 +2666,14 @@
 const int32_t InputDeviceTest::DEVICE_ID = END_RESERVED_ID + 1000;
 const int32_t InputDeviceTest::DEVICE_GENERATION = 2;
 const int32_t InputDeviceTest::DEVICE_CONTROLLER_NUMBER = 0;
-const Flags<InputDeviceClass> InputDeviceTest::DEVICE_CLASSES =
+const ftl::Flags<InputDeviceClass> InputDeviceTest::DEVICE_CLASSES =
         InputDeviceClass::KEYBOARD | InputDeviceClass::TOUCH | InputDeviceClass::JOYSTICK;
 const int32_t InputDeviceTest::EVENTHUB_ID = 1;
 
 TEST_F(InputDeviceTest, ImmutableProperties) {
     ASSERT_EQ(DEVICE_ID, mDevice->getId());
     ASSERT_STREQ(DEVICE_NAME, mDevice->getName().c_str());
-    ASSERT_EQ(Flags<InputDeviceClass>(0), mDevice->getClasses());
+    ASSERT_EQ(ftl::Flags<InputDeviceClass>(0), mDevice->getClasses());
 }
 
 TEST_F(InputDeviceTest, WhenDeviceCreated_EnabledIsFalse) {
@@ -2912,7 +2917,7 @@
     static const int32_t DEVICE_ID;
     static const int32_t DEVICE_GENERATION;
     static const int32_t DEVICE_CONTROLLER_NUMBER;
-    static const Flags<InputDeviceClass> DEVICE_CLASSES;
+    static const ftl::Flags<InputDeviceClass> DEVICE_CLASSES;
     static const int32_t EVENTHUB_ID;
 
     std::shared_ptr<FakeEventHub> mFakeEventHub;
@@ -2921,7 +2926,7 @@
     std::unique_ptr<InstrumentedInputReader> mReader;
     std::shared_ptr<InputDevice> mDevice;
 
-    virtual void SetUp(Flags<InputDeviceClass> classes) {
+    virtual void SetUp(ftl::Flags<InputDeviceClass> classes) {
         mFakeEventHub = std::make_unique<FakeEventHub>();
         mFakePolicy = new FakeInputReaderPolicy();
         mFakeListener = std::make_unique<TestInputListener>();
@@ -2953,7 +2958,7 @@
 
     std::shared_ptr<InputDevice> newDevice(int32_t deviceId, const std::string& name,
                                            const std::string& location, int32_t eventHubId,
-                                           Flags<InputDeviceClass> classes) {
+                                           ftl::Flags<InputDeviceClass> classes) {
         InputDeviceIdentifier identifier;
         identifier.name = name;
         identifier.location = location;
@@ -3045,8 +3050,8 @@
 const int32_t InputMapperTest::DEVICE_ID = END_RESERVED_ID + 1000;
 const int32_t InputMapperTest::DEVICE_GENERATION = 2;
 const int32_t InputMapperTest::DEVICE_CONTROLLER_NUMBER = 0;
-const Flags<InputDeviceClass> InputMapperTest::DEVICE_CLASSES =
-        Flags<InputDeviceClass>(0); // not needed for current tests
+const ftl::Flags<InputDeviceClass> InputMapperTest::DEVICE_CLASSES =
+        ftl::Flags<InputDeviceClass>(0); // not needed for current tests
 const int32_t InputMapperTest::EVENTHUB_ID = 1;
 
 // --- SwitchInputMapperTest ---
@@ -3842,7 +3847,7 @@
     constexpr int32_t SECOND_EVENTHUB_ID = EVENTHUB_ID + 1;
     std::shared_ptr<InputDevice> device2 =
             newDevice(SECOND_DEVICE_ID, DEVICE_NAME2, USB2, SECOND_EVENTHUB_ID,
-                      Flags<InputDeviceClass>(0));
+                      ftl::Flags<InputDeviceClass>(0));
 
     mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0);
     mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_RIGHT, 0, AKEYCODE_DPAD_RIGHT, 0);
@@ -3954,7 +3959,7 @@
     constexpr int32_t SECOND_EVENTHUB_ID = EVENTHUB_ID + 1;
     std::shared_ptr<InputDevice> device2 =
             newDevice(SECOND_DEVICE_ID, DEVICE_NAME2, USB2, SECOND_EVENTHUB_ID,
-                      Flags<InputDeviceClass>(0));
+                      ftl::Flags<InputDeviceClass>(0));
     mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_CAPSL, true /*initially on*/);
     mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_NUML, false /*initially off*/);
     mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_SCROLLL, false /*initially off*/);
@@ -3994,6 +3999,78 @@
     ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper.getMetaState());
 }
 
+TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleInMultiDevices) {
+    // keyboard 1.
+    mFakeEventHub->addLed(EVENTHUB_ID, LED_CAPSL, true /*initially on*/);
+    mFakeEventHub->addLed(EVENTHUB_ID, LED_NUML, false /*initially off*/);
+    mFakeEventHub->addLed(EVENTHUB_ID, LED_SCROLLL, false /*initially off*/);
+    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);
+
+    KeyboardInputMapper& mapper1 =
+            addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+
+    // keyboard 2.
+    const std::string USB2 = "USB2";
+    const std::string DEVICE_NAME2 = "KEYBOARD2";
+    constexpr int32_t SECOND_DEVICE_ID = DEVICE_ID + 1;
+    constexpr int32_t SECOND_EVENTHUB_ID = EVENTHUB_ID + 1;
+    std::shared_ptr<InputDevice> device2 =
+            newDevice(SECOND_DEVICE_ID, DEVICE_NAME2, USB2, SECOND_EVENTHUB_ID,
+                      ftl::Flags<InputDeviceClass>(0));
+    mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_CAPSL, true /*initially on*/);
+    mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_NUML, false /*initially off*/);
+    mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_SCROLLL, false /*initially off*/);
+    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0);
+    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0);
+    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
+
+    KeyboardInputMapper& mapper2 =
+            device2->addMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD,
+                                                    AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0 /*changes*/);
+    device2->reset(ARBITRARY_TIME);
+
+    // Initial metastate is AMETA_NUM_LOCK_ON, turn it off.
+    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
+    ASSERT_EQ(AMETA_NUM_LOCK_ON, mapper1.getMetaState());
+    ASSERT_EQ(AMETA_NUM_LOCK_ON, mapper2.getMetaState());
+
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1);
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0);
+    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
+    ASSERT_EQ(AMETA_NONE, mapper1.getMetaState());
+    ASSERT_EQ(AMETA_NONE, mapper2.getMetaState());
+
+    // Toggle caps lock on and off.
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1);
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0);
+    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
+    ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper1.getMetaState());
+    ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper2.getMetaState());
+
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1);
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0);
+    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
+    ASSERT_EQ(AMETA_NONE, mapper1.getMetaState());
+    ASSERT_EQ(AMETA_NONE, mapper2.getMetaState());
+
+    // Toggle scroll lock on and off.
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1);
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0);
+    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
+    ASSERT_EQ(AMETA_SCROLL_LOCK_ON, mapper1.getMetaState());
+    ASSERT_EQ(AMETA_SCROLL_LOCK_ON, mapper2.getMetaState());
+
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1);
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0);
+    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
+    ASSERT_EQ(AMETA_NONE, mapper1.getMetaState());
+    ASSERT_EQ(AMETA_NONE, mapper2.getMetaState());
+}
+
 // --- KeyboardInputMapperTest_ExternalDevice ---
 
 class KeyboardInputMapperTest_ExternalDevice : public InputMapperTest {
@@ -8409,7 +8486,7 @@
     constexpr int32_t SECOND_EVENTHUB_ID = EVENTHUB_ID + 1;
     std::shared_ptr<InputDevice> device2 =
             newDevice(SECOND_DEVICE_ID, DEVICE_NAME2, USB2, SECOND_EVENTHUB_ID,
-                      Flags<InputDeviceClass>(0));
+                      ftl::Flags<InputDeviceClass>(0));
 
     mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX,
                                    0 /*flat*/, 0 /*fuzz*/);
@@ -9350,7 +9427,7 @@
     static const int32_t DEVICE_ID;
     static const int32_t DEVICE_GENERATION;
     static const int32_t DEVICE_CONTROLLER_NUMBER;
-    static const Flags<InputDeviceClass> DEVICE_CLASSES;
+    static const ftl::Flags<InputDeviceClass> DEVICE_CLASSES;
     static const int32_t EVENTHUB_ID;
 
     std::shared_ptr<FakeEventHub> mFakeEventHub;
@@ -9359,7 +9436,7 @@
     std::unique_ptr<InstrumentedInputReader> mReader;
     std::shared_ptr<InputDevice> mDevice;
 
-    virtual void SetUp(Flags<InputDeviceClass> classes) {
+    virtual void SetUp(ftl::Flags<InputDeviceClass> classes) {
         mFakeEventHub = std::make_unique<FakeEventHub>();
         mFakePolicy = new FakeInputReaderPolicy();
         mFakeListener = std::make_unique<TestInputListener>();
@@ -9385,7 +9462,7 @@
 
     std::shared_ptr<InputDevice> newDevice(int32_t deviceId, const std::string& name,
                                            const std::string& location, int32_t eventHubId,
-                                           Flags<InputDeviceClass> classes) {
+                                           ftl::Flags<InputDeviceClass> classes) {
         InputDeviceIdentifier identifier;
         identifier.name = name;
         identifier.location = location;
@@ -9411,8 +9488,8 @@
 const int32_t PeripheralControllerTest::DEVICE_ID = END_RESERVED_ID + 1000;
 const int32_t PeripheralControllerTest::DEVICE_GENERATION = 2;
 const int32_t PeripheralControllerTest::DEVICE_CONTROLLER_NUMBER = 0;
-const Flags<InputDeviceClass> PeripheralControllerTest::DEVICE_CLASSES =
-        Flags<InputDeviceClass>(0); // not needed for current tests
+const ftl::Flags<InputDeviceClass> PeripheralControllerTest::DEVICE_CLASSES =
+        ftl::Flags<InputDeviceClass>(0); // not needed for current tests
 const int32_t PeripheralControllerTest::EVENTHUB_ID = 1;
 
 // --- BatteryControllerTest ---
diff --git a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
new file mode 100644
index 0000000..8e2ab88
--- /dev/null
+++ b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
@@ -0,0 +1,502 @@
+/*
+ * 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 "../PreferStylusOverTouchBlocker.h"
+
+namespace android {
+
+constexpr int32_t TOUCH_DEVICE_ID = 3;
+constexpr int32_t SECOND_TOUCH_DEVICE_ID = 4;
+constexpr int32_t STYLUS_DEVICE_ID = 5;
+constexpr int32_t SECOND_STYLUS_DEVICE_ID = 6;
+
+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;
+static constexpr int32_t POINTER_1_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+constexpr int32_t TOUCHSCREEN = AINPUT_SOURCE_TOUCHSCREEN;
+constexpr int32_t STYLUS = AINPUT_SOURCE_STYLUS;
+
+struct PointerData {
+    float x;
+    float y;
+};
+
+static NotifyMotionArgs generateMotionArgs(nsecs_t downTime, nsecs_t eventTime, int32_t action,
+                                           const std::vector<PointerData>& points,
+                                           uint32_t source) {
+    size_t pointerCount = points.size();
+    if (action == DOWN || action == UP) {
+        EXPECT_EQ(1U, pointerCount) << "Actions DOWN and UP can only contain a single pointer";
+    }
+
+    PointerProperties pointerProperties[pointerCount];
+    PointerCoords pointerCoords[pointerCount];
+
+    const int32_t deviceId = isFromSource(source, TOUCHSCREEN) ? TOUCH_DEVICE_ID : STYLUS_DEVICE_ID;
+    const int32_t toolType = isFromSource(source, TOUCHSCREEN) ? AMOTION_EVENT_TOOL_TYPE_FINGER
+                                                               : AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    for (size_t i = 0; i < pointerCount; i++) {
+        pointerProperties[i].clear();
+        pointerProperties[i].id = i;
+        pointerProperties[i].toolType = toolType;
+
+        pointerCoords[i].clear();
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, points[i].x);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, points[i].y);
+    }
+
+    // Currently, can't have STYLUS source without it also being a TOUCH source. Update the source
+    // accordingly.
+    if (isFromSource(source, STYLUS)) {
+        source |= TOUCHSCREEN;
+    }
+
+    // Define a valid motion event.
+    NotifyMotionArgs args(/* id */ 0, eventTime, 0 /*readTime*/, deviceId, source, 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;
+}
+
+class PreferStylusOverTouchTest : public testing::Test {
+protected:
+    void assertNotBlocked(const NotifyMotionArgs& args) { assertResponse(args, {args}); }
+
+    void assertDropped(const NotifyMotionArgs& args) { assertResponse(args, {}); }
+
+    void assertResponse(const NotifyMotionArgs& args,
+                        const std::vector<NotifyMotionArgs>& expected) {
+        std::vector<NotifyMotionArgs> receivedArgs = mBlocker.processMotion(args);
+        ASSERT_EQ(expected.size(), receivedArgs.size());
+        for (size_t i = 0; i < expected.size(); i++) {
+            // The 'eventTime' of CANCEL events is dynamically generated. Don't check this field.
+            if (expected[i].action == CANCEL && receivedArgs[i].action == CANCEL) {
+                receivedArgs[i].eventTime = expected[i].eventTime;
+            }
+
+            ASSERT_EQ(expected[i], receivedArgs[i])
+                    << expected[i].dump() << " vs " << receivedArgs[i].dump();
+        }
+    }
+
+    void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& devices) {
+        mBlocker.notifyInputDevicesChanged(devices);
+    }
+
+    void dump() const { ALOGI("Blocker: \n%s\n", mBlocker.dump().c_str()); }
+
+private:
+    PreferStylusOverTouchBlocker mBlocker;
+};
+
+TEST_F(PreferStylusOverTouchTest, TouchGestureIsNotBlocked) {
+    NotifyMotionArgs args;
+
+    args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+    assertNotBlocked(args);
+
+    args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
+    assertNotBlocked(args);
+
+    args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN);
+    assertNotBlocked(args);
+}
+
+TEST_F(PreferStylusOverTouchTest, StylusGestureIsNotBlocked) {
+    NotifyMotionArgs args;
+
+    args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, STYLUS);
+    assertNotBlocked(args);
+
+    args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, STYLUS);
+    assertNotBlocked(args);
+
+    args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{1, 3}}, STYLUS);
+    assertNotBlocked(args);
+}
+
+/**
+ * Existing touch gesture should be canceled when stylus goes down. There should be an ACTION_CANCEL
+ * event generated.
+ */
+TEST_F(PreferStylusOverTouchTest, TouchIsCanceledWhenStylusGoesDown) {
+    NotifyMotionArgs args;
+
+    args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+    assertNotBlocked(args);
+
+    args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
+    assertNotBlocked(args);
+
+    args = generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
+    NotifyMotionArgs cancelArgs =
+            generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, CANCEL, {{1, 3}}, TOUCHSCREEN);
+    cancelArgs.flags |= AMOTION_EVENT_FLAG_CANCELED;
+    assertResponse(args, {cancelArgs, args});
+
+    // Both stylus and touch events continue. Stylus should be not blocked, and touch should be
+    // blocked
+    args = generateMotionArgs(3 /*downTime*/, 4 /*eventTime*/, MOVE, {{10, 31}}, STYLUS);
+    assertNotBlocked(args);
+
+    args = generateMotionArgs(0 /*downTime*/, 5 /*eventTime*/, MOVE, {{1, 4}}, TOUCHSCREEN);
+    assertDropped(args);
+}
+
+/**
+ * Stylus goes down after touch gesture.
+ */
+TEST_F(PreferStylusOverTouchTest, StylusDownAfterTouch) {
+    NotifyMotionArgs args;
+
+    args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+    assertNotBlocked(args);
+
+    args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
+    assertNotBlocked(args);
+
+    args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN);
+    assertNotBlocked(args);
+
+    // Stylus goes down
+    args = generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
+    assertNotBlocked(args);
+}
+
+/**
+ * New touch events should be simply blocked (dropped) when stylus is down. No CANCEL event should
+ * be generated.
+ */
+TEST_F(PreferStylusOverTouchTest, NewTouchIsBlockedWhenStylusIsDown) {
+    NotifyMotionArgs args;
+    constexpr nsecs_t stylusDownTime = 0;
+    constexpr nsecs_t touchDownTime = 1;
+
+    args = generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
+    assertNotBlocked(args);
+
+    args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+    assertDropped(args);
+
+    // Stylus should continue to work
+    args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, MOVE, {{10, 31}}, STYLUS);
+    assertNotBlocked(args);
+
+    // Touch should continue to be blocked
+    args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
+    assertDropped(args);
+
+    args = generateMotionArgs(0 /*downTime*/, 5 /*eventTime*/, MOVE, {{1, 4}}, TOUCHSCREEN);
+    assertDropped(args);
+}
+
+/**
+ * New touch events should be simply blocked (dropped) when stylus is down. No CANCEL event should
+ * be generated.
+ */
+TEST_F(PreferStylusOverTouchTest, NewTouchWorksAfterStylusIsLifted) {
+    NotifyMotionArgs args;
+    constexpr nsecs_t stylusDownTime = 0;
+    constexpr nsecs_t touchDownTime = 4;
+
+    // Stylus goes down and up
+    args = generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
+    assertNotBlocked(args);
+
+    args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, MOVE, {{10, 31}}, STYLUS);
+    assertNotBlocked(args);
+
+    args = generateMotionArgs(stylusDownTime, 3 /*eventTime*/, UP, {{10, 31}}, STYLUS);
+    assertNotBlocked(args);
+
+    // New touch goes down. It should not be blocked
+    args = generateMotionArgs(touchDownTime, touchDownTime, DOWN, {{1, 2}}, TOUCHSCREEN);
+    assertNotBlocked(args);
+
+    args = generateMotionArgs(touchDownTime, 5 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
+    assertNotBlocked(args);
+
+    args = generateMotionArgs(touchDownTime, 6 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN);
+    assertNotBlocked(args);
+}
+
+/**
+ * Once a touch gesture is canceled, it should continue to be canceled, even if the stylus has been
+ * lifted.
+ */
+TEST_F(PreferStylusOverTouchTest, AfterStylusIsLiftedCurrentTouchIsBlocked) {
+    NotifyMotionArgs args;
+    constexpr nsecs_t stylusDownTime = 0;
+    constexpr nsecs_t touchDownTime = 1;
+
+    assertNotBlocked(generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS));
+
+    args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+    assertDropped(args);
+
+    // Lift the stylus
+    args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, UP, {{10, 30}}, STYLUS);
+    assertNotBlocked(args);
+
+    // Touch should continue to be blocked
+    args = generateMotionArgs(touchDownTime, 3 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
+    assertDropped(args);
+
+    args = generateMotionArgs(touchDownTime, 4 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN);
+    assertDropped(args);
+
+    // New touch should go through, though.
+    constexpr nsecs_t newTouchDownTime = 5;
+    args = generateMotionArgs(newTouchDownTime, 5 /*eventTime*/, DOWN, {{10, 20}}, TOUCHSCREEN);
+    assertNotBlocked(args);
+}
+
+/**
+ * If an event with mixed stylus and touch pointers is encountered, it should be ignored. Touches
+ * from such should pass, even if stylus from the same device goes down.
+ */
+TEST_F(PreferStylusOverTouchTest, MixedStylusAndTouchPointersAreIgnored) {
+    NotifyMotionArgs args;
+
+    // Event from a stylus device, but with finger tool type
+    args = generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, STYLUS);
+    // Keep source stylus, but make the tool type touch
+    args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+    assertNotBlocked(args);
+
+    // Second pointer (stylus pointer) goes down, from the same device
+    args = generateMotionArgs(1 /*downTime*/, 2 /*eventTime*/, POINTER_1_DOWN, {{1, 2}, {10, 20}},
+                              STYLUS);
+    // Keep source stylus, but make the tool type touch
+    args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    assertNotBlocked(args);
+
+    // Second pointer (stylus pointer) goes down, from the same device
+    args = generateMotionArgs(1 /*downTime*/, 3 /*eventTime*/, MOVE, {{2, 3}, {11, 21}}, STYLUS);
+    // Keep source stylus, but make the tool type touch
+    args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+    assertNotBlocked(args);
+}
+
+/**
+ * When there are two touch devices, stylus down should cancel all current touch streams.
+ */
+TEST_F(PreferStylusOverTouchTest, TouchFromTwoDevicesAndStylus) {
+    NotifyMotionArgs touch1Down =
+            generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+    assertNotBlocked(touch1Down);
+
+    NotifyMotionArgs touch2Down =
+            generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{3, 4}}, TOUCHSCREEN);
+    touch2Down.deviceId = SECOND_TOUCH_DEVICE_ID;
+    assertNotBlocked(touch2Down);
+
+    NotifyMotionArgs stylusDown =
+            generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
+    NotifyMotionArgs cancelArgs1 = touch1Down;
+    cancelArgs1.action = CANCEL;
+    cancelArgs1.flags |= AMOTION_EVENT_FLAG_CANCELED;
+    NotifyMotionArgs cancelArgs2 = touch2Down;
+    cancelArgs2.action = CANCEL;
+    cancelArgs2.flags |= AMOTION_EVENT_FLAG_CANCELED;
+    assertResponse(stylusDown, {cancelArgs1, cancelArgs2, stylusDown});
+}
+
+/**
+ * Touch should be canceled when stylus goes down. After the stylus lifts up, the touch from that
+ * device should continue to be canceled.
+ * If one of the devices is already canceled, it should remain canceled, but new touches from a
+ * different device should go through.
+ */
+TEST_F(PreferStylusOverTouchTest, AllTouchMustLiftAfterCanceledByStylus) {
+    // First device touches down
+    NotifyMotionArgs touch1Down =
+            generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+    assertNotBlocked(touch1Down);
+
+    // Stylus goes down - touch should be canceled
+    NotifyMotionArgs stylusDown =
+            generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
+    NotifyMotionArgs cancelArgs1 = touch1Down;
+    cancelArgs1.action = CANCEL;
+    cancelArgs1.flags |= AMOTION_EVENT_FLAG_CANCELED;
+    assertResponse(stylusDown, {cancelArgs1, stylusDown});
+
+    // Stylus goes up
+    NotifyMotionArgs stylusUp =
+            generateMotionArgs(2 /*downTime*/, 3 /*eventTime*/, UP, {{10, 30}}, STYLUS);
+    assertNotBlocked(stylusUp);
+
+    // Touch from the first device remains blocked
+    NotifyMotionArgs touch1Move =
+            generateMotionArgs(1 /*downTime*/, 4 /*eventTime*/, MOVE, {{2, 3}}, TOUCHSCREEN);
+    assertDropped(touch1Move);
+
+    // Second touch goes down. It should not be blocked because stylus has already lifted.
+    NotifyMotionArgs touch2Down =
+            generateMotionArgs(5 /*downTime*/, 5 /*eventTime*/, DOWN, {{31, 32}}, TOUCHSCREEN);
+    touch2Down.deviceId = SECOND_TOUCH_DEVICE_ID;
+    assertNotBlocked(touch2Down);
+
+    // First device is lifted up. It's already been canceled, so the UP event should be dropped.
+    NotifyMotionArgs touch1Up =
+            generateMotionArgs(1 /*downTime*/, 6 /*eventTime*/, UP, {{2, 3}}, TOUCHSCREEN);
+    assertDropped(touch1Up);
+
+    // Touch from second device touch should continue to work
+    NotifyMotionArgs touch2Move =
+            generateMotionArgs(5 /*downTime*/, 7 /*eventTime*/, MOVE, {{32, 33}}, TOUCHSCREEN);
+    touch2Move.deviceId = SECOND_TOUCH_DEVICE_ID;
+    assertNotBlocked(touch2Move);
+
+    // Second touch lifts up
+    NotifyMotionArgs touch2Up =
+            generateMotionArgs(5 /*downTime*/, 8 /*eventTime*/, UP, {{32, 33}}, TOUCHSCREEN);
+    touch2Up.deviceId = SECOND_TOUCH_DEVICE_ID;
+    assertNotBlocked(touch2Up);
+
+    // Now that all touch has been lifted, new touch from either first or second device should work
+    NotifyMotionArgs touch3Down =
+            generateMotionArgs(9 /*downTime*/, 9 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+    assertNotBlocked(touch3Down);
+
+    NotifyMotionArgs touch4Down =
+            generateMotionArgs(10 /*downTime*/, 10 /*eventTime*/, DOWN, {{100, 200}}, TOUCHSCREEN);
+    touch4Down.deviceId = SECOND_TOUCH_DEVICE_ID;
+    assertNotBlocked(touch4Down);
+}
+
+/**
+ * When we don't know that a specific device does both stylus and touch, and we only see touch
+ * pointers from it, we should treat it as a touch device. That means, the device events should be
+ * canceled when stylus from another device goes down. When we detect simultaneous touch and stylus
+ * from this device though, we should just pass this device through without canceling anything.
+ *
+ * In this test:
+ * 1. Start by touching down with device 1
+ * 2. Device 2 has stylus going down
+ * 3. Device 1 should be canceled.
+ * 4. When we add stylus pointers to the device 1, they should continue to be canceled.
+ * 5. Device 1 lifts up.
+ * 6. Subsequent events from device 1 should not be canceled even if stylus is down.
+ * 7. If a reset happens, and such device is no longer there, then we should
+ * Therefore, the device 1 is "ignored" and does not participate into "prefer stylus over touch"
+ * behaviour.
+ */
+TEST_F(PreferStylusOverTouchTest, MixedStylusAndTouchDeviceIsCanceledAtFirst) {
+    // Touch from device 1 goes down
+    NotifyMotionArgs touchDown =
+            generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+    touchDown.source = STYLUS;
+    assertNotBlocked(touchDown);
+
+    // Stylus from device 2 goes down. Touch should be canceled.
+    NotifyMotionArgs args =
+            generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{10, 20}}, STYLUS);
+    NotifyMotionArgs cancelTouchArgs = touchDown;
+    cancelTouchArgs.action = CANCEL;
+    cancelTouchArgs.flags |= AMOTION_EVENT_FLAG_CANCELED;
+    assertResponse(args, {cancelTouchArgs, args});
+
+    // Introduce a stylus pointer into the device 1 stream. It should be ignored.
+    args = generateMotionArgs(1 /*downTime*/, 3 /*eventTime*/, POINTER_1_DOWN, {{1, 2}, {3, 4}},
+                              TOUCHSCREEN);
+    args.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    args.source = STYLUS;
+    assertDropped(args);
+
+    // Lift up touch from the mixed touch/stylus device
+    args = generateMotionArgs(1 /*downTime*/, 4 /*eventTime*/, CANCEL, {{1, 2}, {3, 4}},
+                              TOUCHSCREEN);
+    args.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    args.source = STYLUS;
+    assertDropped(args);
+
+    // Stylus from device 2 is still down. Since the device 1 is now identified as a mixed
+    // touch/stylus device, its events should go through, even if they are touch.
+    args = generateMotionArgs(5 /*downTime*/, 5 /*eventTime*/, DOWN, {{21, 22}}, TOUCHSCREEN);
+    touchDown.source = STYLUS;
+    assertResponse(args, {args});
+
+    // Reconfigure such that only the stylus device remains
+    InputDeviceInfo stylusDevice;
+    stylusDevice.initialize(STYLUS_DEVICE_ID, 1 /*generation*/, 1 /*controllerNumber*/,
+                            {} /*identifier*/, "stylus device", false /*external*/,
+                            false /*hasMic*/);
+    notifyInputDevicesChanged({stylusDevice});
+    // The touchscreen device was removed, so we no longer remember anything about it. We should
+    // again start blocking touch events from it.
+    args = generateMotionArgs(6 /*downTime*/, 6 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+    args.source = STYLUS;
+    assertDropped(args);
+}
+
+/**
+ * If two styli are active at the same time, touch should be blocked until both of them are lifted.
+ * If one of them lifts, touch should continue to be blocked.
+ */
+TEST_F(PreferStylusOverTouchTest, TouchIsBlockedWhenTwoStyliAreUsed) {
+    NotifyMotionArgs args;
+
+    // First stylus is down
+    assertNotBlocked(generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS));
+
+    // Second stylus is down
+    args = generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{20, 40}}, STYLUS);
+    args.deviceId = SECOND_STYLUS_DEVICE_ID;
+    assertNotBlocked(args);
+
+    // Touch goes down. It should be ignored.
+    args = generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+    assertDropped(args);
+
+    // Lift the first stylus
+    args = generateMotionArgs(0 /*downTime*/, 3 /*eventTime*/, UP, {{10, 30}}, STYLUS);
+    assertNotBlocked(args);
+
+    // Touch should continue to be blocked
+    args = generateMotionArgs(2 /*downTime*/, 4 /*eventTime*/, UP, {{1, 2}}, TOUCHSCREEN);
+    assertDropped(args);
+
+    // New touch should be blocked because second stylus is still down
+    args = generateMotionArgs(5 /*downTime*/, 5 /*eventTime*/, DOWN, {{5, 6}}, TOUCHSCREEN);
+    assertDropped(args);
+
+    // Second stylus goes up
+    args = generateMotionArgs(1 /*downTime*/, 6 /*eventTime*/, UP, {{20, 40}}, STYLUS);
+    args.deviceId = SECOND_STYLUS_DEVICE_ID;
+    assertNotBlocked(args);
+
+    // Current touch gesture should continue to be blocked
+    // Touch should continue to be blocked
+    args = generateMotionArgs(5 /*downTime*/, 7 /*eventTime*/, UP, {{5, 6}}, TOUCHSCREEN);
+    assertDropped(args);
+
+    // Now that all styli were lifted, new touch should go through
+    args = generateMotionArgs(8 /*downTime*/, 8 /*eventTime*/, DOWN, {{7, 8}}, TOUCHSCREEN);
+    assertNotBlocked(args);
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
index a3220cc..e378096 100644
--- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
+++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
@@ -490,6 +490,14 @@
             &(args = generateMotionArgs(0 /*downTime*/, 4 /*eventTime*/, DOWN, {{7, 8, 9}})));
 }
 
+TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenStylusSourceWithFingerToolIsReceived) {
+    mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
+    NotifyMotionArgs args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}});
+    args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+    args.source = AINPUT_SOURCE_STYLUS;
+    mBlocker->notifyMotion(&args);
+}
+
 /**
  * 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.
@@ -511,6 +519,34 @@
             &(args = generateMotionArgs(0 /*downTime*/, 4 /*eventTime*/, MOVE, {{7, 8, 9}})));
 }
 
+/**
+ * Send a touch event, and then a stylus event. Make sure that both work.
+ */
+TEST_F(UnwantedInteractionBlockerTest, StylusAfterTouchWorks) {
+    NotifyMotionArgs args;
+    mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
+    args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}});
+    mBlocker->notifyMotion(&args);
+    args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{4, 5, 6}});
+    mBlocker->notifyMotion(&args);
+    args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{4, 5, 6}});
+    mBlocker->notifyMotion(&args);
+
+    // Now touch down stylus
+    args = generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 20, 30}});
+    args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    args.source |= AINPUT_SOURCE_STYLUS;
+    mBlocker->notifyMotion(&args);
+    args = generateMotionArgs(3 /*downTime*/, 4 /*eventTime*/, MOVE, {{40, 50, 60}});
+    args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    args.source |= AINPUT_SOURCE_STYLUS;
+    mBlocker->notifyMotion(&args);
+    args = generateMotionArgs(3 /*downTime*/, 5 /*eventTime*/, UP, {{40, 50, 60}});
+    args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    args.source |= AINPUT_SOURCE_STYLUS;
+    mBlocker->notifyMotion(&args);
+}
+
 using UnwantedInteractionBlockerTestDeathTest = UnwantedInteractionBlockerTest;
 
 /**
diff --git a/services/sensorservice/HidlSensorHalWrapper.cpp b/services/sensorservice/HidlSensorHalWrapper.cpp
index 4c64e59..c55c9b4 100644
--- a/services/sensorservice/HidlSensorHalWrapper.cpp
+++ b/services/sensorservice/HidlSensorHalWrapper.cpp
@@ -281,7 +281,7 @@
 }
 
 status_t HidlSensorHalWrapper::registerDirectChannel(const sensors_direct_mem_t* memory,
-                                                     int32_t* /*channelHandle*/) {
+                                                     int32_t* outChannelHandle) {
     if (mSensors == nullptr) return NO_INIT;
 
     SharedMemType type;
@@ -309,14 +309,16 @@
             .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);
-        }
-    }));
+    status_t ret = OK;
+    checkReturn(mSensors->registerDirectChannel(mem,
+                                                [&ret, &outChannelHandle](auto result,
+                                                                          auto channelHandle) {
+                                                    if (result == Result::OK) {
+                                                        *outChannelHandle = channelHandle;
+                                                    } else {
+                                                        ret = statusFromResult(result);
+                                                    }
+                                                }));
     return ret;
 }
 
diff --git a/services/sensorservice/HidlSensorHalWrapper.h b/services/sensorservice/HidlSensorHalWrapper.h
index 71c3512..d6ed178 100644
--- a/services/sensorservice/HidlSensorHalWrapper.h
+++ b/services/sensorservice/HidlSensorHalWrapper.h
@@ -112,7 +112,7 @@
     virtual status_t injectSensorData(const sensors_event_t* event) override;
 
     virtual status_t registerDirectChannel(const sensors_direct_mem_t* memory,
-                                           int32_t* channelHandle) override;
+                                           int32_t* outChannelHandle) override;
 
     virtual status_t unregisterDirectChannel(int32_t channelHandle) override;
 
diff --git a/services/sensorservice/SensorDevice.cpp b/services/sensorservice/SensorDevice.cpp
index a0e30ac..53a3025 100644
--- a/services/sensorservice/SensorDevice.cpp
+++ b/services/sensorservice/SensorDevice.cpp
@@ -766,7 +766,13 @@
     if (mHalWrapper == nullptr) return NO_INIT;
     Mutex::Autolock _l(mLock);
 
-    return mHalWrapper->registerDirectChannel(memory, nullptr);
+    int32_t channelHandle;
+    status_t status = mHalWrapper->registerDirectChannel(memory, &channelHandle);
+    if (status != OK) {
+        channelHandle = -1;
+    }
+
+    return channelHandle;
 }
 
 void SensorDevice::unregisterDirectChannel(int32_t channelHandle) {
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index 3c164aa..8b81d48 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -2030,7 +2030,9 @@
     // Runtime permissions can't use the cache as they may change.
     if (sensor.isRequiredPermissionRuntime()) {
         hasPermission = checkPermission(String16(requiredPermission),
-                IPCThreadState::self()->getCallingPid(), IPCThreadState::self()->getCallingUid());
+                IPCThreadState::self()->getCallingPid(),
+                IPCThreadState::self()->getCallingUid(),
+                /*logPermissionFailure=*/ false);
     } else {
         hasPermission = PermissionCache::checkCallingPermission(String16(requiredPermission));
     }
@@ -2211,7 +2213,8 @@
     int targetSdk = getTargetSdkVersion(opPackageName);
     bool hasSamplingRatePermission = checkPermission(sAccessHighSensorSamplingRatePermission,
             IPCThreadState::self()->getCallingPid(),
-            IPCThreadState::self()->getCallingUid());
+            IPCThreadState::self()->getCallingUid(),
+            /*logPermissionFailure=*/ false);
     if (targetSdk < __ANDROID_API_S__ ||
             (targetSdk >= __ANDROID_API_S__ && hasSamplingRatePermission)) {
         return false;
diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h
index 7194db3..b18d204 100644
--- a/services/sensorservice/SensorService.h
+++ b/services/sensorservice/SensorService.h
@@ -257,11 +257,13 @@
 
             bool isUidActive(uid_t uid);
 
-            void onUidGone(uid_t uid, bool disabled);
-            void onUidActive(uid_t uid);
-            void onUidIdle(uid_t uid, bool disabled);
+            void onUidGone(uid_t uid, bool disabled) override;
+            void onUidActive(uid_t uid) override;
+            void onUidIdle(uid_t uid, bool disabled) override;
             void onUidStateChanged(uid_t uid __unused, int32_t procState __unused,
-                                   int64_t procStateSeq __unused, int32_t capability __unused) {}
+                                   int64_t procStateSeq __unused,
+                                   int32_t capability __unused) override {}
+            void onUidProcAdjChanged(uid_t uid __unused) override {}
 
             void addOverrideUid(uid_t uid, bool active);
             void removeOverrideUid(uid_t uid);
diff --git a/services/sensorservice/SensorServiceUtils.cpp b/services/sensorservice/SensorServiceUtils.cpp
index 7dd2331..6bad962 100644
--- a/services/sensorservice/SensorServiceUtils.cpp
+++ b/services/sensorservice/SensorServiceUtils.cpp
@@ -39,6 +39,7 @@
             return 5;
 
         case SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED:
+        case SENSOR_TYPE_ACCELEROMETER_UNCALIBRATED:
         case SENSOR_TYPE_GYROSCOPE_UNCALIBRATED:
         case SENSOR_TYPE_ACCELEROMETER_LIMITED_AXES:
         case SENSOR_TYPE_GYROSCOPE_LIMITED_AXES:
diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp
index bcae8d9..c5d7a60 100644
--- a/services/surfaceflinger/BufferStateLayer.cpp
+++ b/services/surfaceflinger/BufferStateLayer.cpp
@@ -1098,6 +1098,13 @@
         }
     }
 
+    if (s.what & layer_state_t::eDimmingEnabledChanged) {
+        if (mDrawingState.dimmingEnabled != s.dimmingEnabled) {
+            ALOGV("%s: false [eDimmingEnabledChanged changed]", __func__);
+            return false;
+        }
+    }
+
     ALOGV("%s: true", __func__);
     return true;
 }
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index aefc014..11a9e19 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -23,6 +23,7 @@
         "android.hardware.graphics.composer3-V1-ndk",
         "android.hardware.power@1.0",
         "android.hardware.power@1.3",
+        "android.hardware.power-V2-cpp",
         "libbase",
         "libcutils",
         "libgui",
@@ -41,6 +42,10 @@
         "libtonemap",
         "libtrace_proto",
         "libaidlcommonsupport",
+        "libprocessgroup",
+        "libcgrouprc",
+        "libjsoncpp",
+        "libcgrouprc_format",
     ],
     header_libs: [
         "android.hardware.graphics.composer@2.1-command-buffer",
@@ -68,6 +73,7 @@
         "src/DisplayColorProfile.cpp",
         "src/DisplaySurface.cpp",
         "src/DumpHelpers.cpp",
+        "src/HwcAsyncWorker.cpp",
         "src/HwcBufferCache.cpp",
         "src/LayerFECompositionState.cpp",
         "src/Output.cpp",
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplaySurface.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplaySurface.h
index c553fce..ca86f4c 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplaySurface.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplaySurface.h
@@ -72,6 +72,9 @@
     virtual void resizeBuffers(const ui::Size&) = 0;
 
     virtual const sp<Fence>& getClientTargetAcquireFence() const = 0;
+
+    // Returns true if the render surface supports client composition prediction.
+    virtual bool supportsCompositionStrategyPrediction() const;
 };
 
 } // namespace compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
index 283fe86..974f7c6 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
@@ -207,6 +207,9 @@
     // framerate of the layer as measured by LayerHistory
     float fps;
 
+    // The dimming flag
+    bool dimmingEnabled{true};
+
     virtual ~LayerFECompositionState();
 
     // Debugging
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
index d8644a4..5846e67 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -35,6 +35,7 @@
 #include <utils/Vector.h>
 
 #include <ui/DisplayIdentification.h>
+#include "DisplayHardware/HWComposer.h"
 
 namespace android {
 
@@ -54,6 +55,7 @@
 
 namespace impl {
 struct OutputCompositionState;
+struct GpuCompositionResult;
 } // namespace impl
 
 /**
@@ -262,6 +264,9 @@
     // Latches the front-end layer state for each output layer
     virtual void updateLayerStateFromFE(const CompositionRefreshArgs&) const = 0;
 
+    // Enables predicting composition strategy to run client composition earlier
+    virtual void setPredictCompositionStrategy(bool) = 0;
+
 protected:
     virtual void setDisplayColorProfile(std::unique_ptr<DisplayColorProfile>) = 0;
     virtual void setRenderSurface(std::unique_ptr<RenderSurface>) = 0;
@@ -278,13 +283,22 @@
     virtual void updateColorProfile(const CompositionRefreshArgs&) = 0;
     virtual void beginFrame() = 0;
     virtual void prepareFrame() = 0;
+
+    using GpuCompositionResult = compositionengine::impl::GpuCompositionResult;
+    // Runs prepare frame in another thread while running client composition using
+    // the previous frame's composition strategy.
+    virtual GpuCompositionResult prepareFrameAsync(const CompositionRefreshArgs&) = 0;
     virtual void devOptRepaintFlash(const CompositionRefreshArgs&) = 0;
-    virtual void finishFrame(const CompositionRefreshArgs&) = 0;
+    virtual void finishFrame(const CompositionRefreshArgs&, GpuCompositionResult&&) = 0;
     virtual std::optional<base::unique_fd> composeSurfaces(
-            const Region&, const compositionengine::CompositionRefreshArgs& refreshArgs) = 0;
+            const Region&, const compositionengine::CompositionRefreshArgs&,
+            std::shared_ptr<renderengine::ExternalTexture>, base::unique_fd&) = 0;
     virtual void postFramebuffer() = 0;
     virtual void renderCachedSets(const CompositionRefreshArgs&) = 0;
-    virtual void chooseCompositionStrategy() = 0;
+    virtual bool chooseCompositionStrategy(
+            std::optional<android::HWComposer::DeviceRequestedChanges>*) = 0;
+    virtual void applyCompositionStrategy(
+            const std::optional<android::HWComposer::DeviceRequestedChanges>& changes) = 0;
     virtual bool getSkipColorTransform() const = 0;
     virtual FrameFences presentAndGetFrameFences() = 0;
     virtual std::vector<LayerFE::LayerSettings> generateClientCompositionRequests(
@@ -295,6 +309,7 @@
             std::vector<LayerFE::LayerSettings>& clientCompositionLayers) = 0;
     virtual void setExpensiveRenderingExpected(bool enabled) = 0;
     virtual void cacheClientCompositionRequests(uint32_t cacheSize) = 0;
+    virtual bool canPredictCompositionStrategy(const CompositionRefreshArgs&) = 0;
 };
 
 } // namespace compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/ProjectionSpace.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/ProjectionSpace.h
index a63145a..49013e0 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/ProjectionSpace.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/ProjectionSpace.h
@@ -120,16 +120,16 @@
 
 } // namespace compositionengine
 
-inline std::string to_string(const android::compositionengine::ProjectionSpace& space) {
-    return android::base::
-            StringPrintf("ProjectionSpace(bounds = %s, content = %s, orientation = %s)",
-                         to_string(space.getBoundsAsRect()).c_str(),
-                         to_string(space.getContent()).c_str(), toCString(space.getOrientation()));
+inline std::string to_string(const compositionengine::ProjectionSpace& space) {
+    return base::StringPrintf("ProjectionSpace{bounds=%s, content=%s, orientation=%s}",
+                              to_string(space.getBoundsAsRect()).c_str(),
+                              to_string(space.getContent()).c_str(),
+                              toCString(space.getOrientation()));
 }
 
 // Defining PrintTo helps with Google Tests.
-inline void PrintTo(const android::compositionengine::ProjectionSpace& space, ::std::ostream* os) {
+inline void PrintTo(const compositionengine::ProjectionSpace& space, std::ostream* os) {
     *os << to_string(space);
 }
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/RenderSurface.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/RenderSurface.h
index daee83b..9ee779c 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/RenderSurface.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/RenderSurface.h
@@ -100,6 +100,9 @@
 
     // Debugging - gets the page flip count for the RenderSurface
     virtual std::uint32_t getPageFlipCount() const = 0;
+
+    // Returns true if the render surface supports client composition prediction.
+    virtual bool supportsCompositionStrategyPrediction() const = 0;
 };
 
 } // namespace compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
index e12d1b4..61a0e6a 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
@@ -22,6 +22,7 @@
 #include <compositionengine/DisplayColorProfile.h>
 #include <compositionengine/DisplayCreationArgs.h>
 #include <compositionengine/RenderSurface.h>
+#include <compositionengine/impl/GpuCompositionResult.h>
 #include <compositionengine/impl/Output.h>
 #include <ui/PixelFormat.h>
 #include <ui/Size.h>
@@ -51,11 +52,16 @@
     void setReleasedLayers(const CompositionRefreshArgs&) override;
     void setColorTransform(const CompositionRefreshArgs&) override;
     void setColorProfile(const ColorProfile&) override;
-    void chooseCompositionStrategy() override;
+
+    void beginFrame() override;
+    using DeviceRequestedChanges = android::HWComposer::DeviceRequestedChanges;
+    bool chooseCompositionStrategy(
+            std::optional<android::HWComposer::DeviceRequestedChanges>*) override;
+    void applyCompositionStrategy(const std::optional<DeviceRequestedChanges>&) override;
     bool getSkipColorTransform() const override;
     compositionengine::Output::FrameFences presentAndGetFrameFences() override;
     void setExpensiveRenderingExpected(bool) override;
-    void finishFrame(const CompositionRefreshArgs&) override;
+    void finishFrame(const CompositionRefreshArgs&, GpuCompositionResult&&) override;
 
     // compositionengine::Display overrides
     DisplayId getId() const override;
@@ -72,12 +78,11 @@
     using DisplayRequests = android::HWComposer::DeviceRequestedChanges::DisplayRequests;
     using LayerRequests = android::HWComposer::DeviceRequestedChanges::LayerRequests;
     using ClientTargetProperty = android::HWComposer::DeviceRequestedChanges::ClientTargetProperty;
-    virtual bool anyLayersRequireClientComposition() const;
     virtual bool allLayersRequireClientComposition() const;
     virtual void applyChangedTypesToLayers(const ChangedTypes&);
     virtual void applyDisplayRequests(const DisplayRequests&);
     virtual void applyLayerRequestsToLayers(const LayerRequests&);
-    virtual void applyClientTargetRequests(const ClientTargetProperty&, float brightness);
+    virtual void applyClientTargetRequests(const ClientTargetProperty&);
 
     // Internal
     virtual void setConfiguration(const compositionengine::DisplayCreationArgs&);
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/GpuCompositionResult.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/GpuCompositionResult.h
new file mode 100644
index 0000000..2b1f50f
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/GpuCompositionResult.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/unique_fd.h>
+#include <ui/GraphicBuffer.h>
+
+namespace android::compositionengine::impl {
+
+struct GpuCompositionResult {
+    // Composition ready fence.
+    base::unique_fd fence{};
+
+    // Buffer to be used for gpu composition. If gpu composition was not successful,
+    // then we want to reuse the buffer instead of dequeuing another buffer.
+    std::shared_ptr<renderengine::ExternalTexture> buffer = nullptr;
+
+    bool bufferAvailable() const { return buffer != nullptr; };
+};
+
+} // namespace android::compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcAsyncWorker.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcAsyncWorker.h
new file mode 100644
index 0000000..0c7da6c
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcAsyncWorker.h
@@ -0,0 +1,55 @@
+/*
+ * 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 <future>
+#include <optional>
+#include <thread>
+
+#include "DisplayHardware/HWComposer.h"
+
+namespace android::compositionengine::impl {
+
+// HWC Validate call may take multiple milliseconds to complete and can account for
+// a signification amount of time in the display hotpath. This helper class allows
+// us to run the hwc validate function on a real time thread if we can predict what
+// the composition strategy will be and if composition includes client composition.
+// While the hwc validate runs, client composition is kicked off with the prediction.
+// When the worker returns with a value, the composition continues if the prediction
+// was successful otherwise the client composition is re-executed.
+//
+// Note: This does not alter the sequence between HWC and surfaceflinger.
+class HwcAsyncWorker final {
+public:
+    HwcAsyncWorker();
+    ~HwcAsyncWorker();
+    // Runs the provided function which calls hwc validate and returns the requested
+    // device changes as a future.
+    std::future<bool> send(std::function<bool()>);
+
+private:
+    std::mutex mMutex;
+    std::condition_variable mCv GUARDED_BY(mMutex);
+    bool mDone GUARDED_BY(mMutex) = false;
+    bool mTaskRequested GUARDED_BY(mMutex) = false;
+    std::packaged_task<bool()> mTask GUARDED_BY(mMutex);
+    std::thread mThread;
+    void run();
+};
+
+} // 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 a7a8e97..0feb9f7 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -17,9 +17,13 @@
 #pragma once
 
 #include <compositionengine/CompositionEngine.h>
+#include <compositionengine/LayerFECompositionState.h>
 #include <compositionengine/Output.h>
 #include <compositionengine/impl/ClientCompositionRequestCache.h>
+#include <compositionengine/impl/GpuCompositionResult.h>
+#include <compositionengine/impl/HwcAsyncWorker.h>
 #include <compositionengine/impl/OutputCompositionState.h>
+#include <compositionengine/impl/OutputLayerCompositionState.h>
 #include <compositionengine/impl/planner/Planner.h>
 #include <renderengine/DisplaySettings.h>
 #include <renderengine/LayerSettings.h>
@@ -92,25 +96,42 @@
     void updateColorProfile(const compositionengine::CompositionRefreshArgs&) override;
     void beginFrame() override;
     void prepareFrame() override;
+    GpuCompositionResult prepareFrameAsync(const CompositionRefreshArgs&) override;
     void devOptRepaintFlash(const CompositionRefreshArgs&) override;
-    void finishFrame(const CompositionRefreshArgs&) override;
-    std::optional<base::unique_fd> composeSurfaces(
-            const Region&, const compositionengine::CompositionRefreshArgs& refreshArgs) override;
+    void finishFrame(const CompositionRefreshArgs&, GpuCompositionResult&&) override;
+    std::optional<base::unique_fd> composeSurfaces(const Region&,
+                                                   const compositionengine::CompositionRefreshArgs&,
+                                                   std::shared_ptr<renderengine::ExternalTexture>,
+                                                   base::unique_fd&) override;
     void postFramebuffer() override;
     void renderCachedSets(const CompositionRefreshArgs&) override;
     void cacheClientCompositionRequests(uint32_t) override;
+    bool canPredictCompositionStrategy(const CompositionRefreshArgs&) override;
+    void setPredictCompositionStrategy(bool) override;
 
     // Testing
     const ReleasedLayers& getReleasedLayersForTest() const;
     void setDisplayColorProfileForTest(std::unique_ptr<compositionengine::DisplayColorProfile>);
     void setRenderSurfaceForTest(std::unique_ptr<compositionengine::RenderSurface>);
     bool plannerEnabled() const { return mPlanner != nullptr; }
+    virtual bool anyLayersRequireClientComposition() const;
+    virtual void updateProtectedContentState();
+    virtual bool dequeueRenderBuffer(base::unique_fd*,
+                                     std::shared_ptr<renderengine::ExternalTexture>*);
+    virtual std::future<bool> chooseCompositionStrategyAsync(
+            std::optional<android::HWComposer::DeviceRequestedChanges>*);
+    virtual void resetCompositionStrategy();
 
 protected:
     std::unique_ptr<compositionengine::OutputLayer> createOutputLayer(const sp<LayerFE>&) const;
     std::optional<size_t> findCurrentOutputLayerForLayer(
             const sp<compositionengine::LayerFE>&) const;
-    void chooseCompositionStrategy() override;
+    using DeviceRequestedChanges = android::HWComposer::DeviceRequestedChanges;
+    bool chooseCompositionStrategy(
+            std::optional<android::HWComposer::DeviceRequestedChanges>*) override {
+        return true;
+    };
+    void applyCompositionStrategy(const std::optional<DeviceRequestedChanges>&) override{};
     bool getSkipColorTransform() const override;
     compositionengine::Output::FrameFences presentAndGetFrameFences() override;
     std::vector<LayerFE::LayerSettings> generateClientCompositionRequests(
@@ -131,6 +152,7 @@
 private:
     void dirtyEntireOutput();
     compositionengine::OutputLayer* findLayerRequestingBackgroundComposition() const;
+    void finishPrepareFrame();
     ui::Dataspace getBestDataspace(ui::Dataspace*, bool*) const;
     compositionengine::Output::ColorProfile pickColorProfile(
             const compositionengine::CompositionRefreshArgs&) const;
@@ -144,6 +166,7 @@
     OutputLayer* mLayerRequestingBackgroundBlur = nullptr;
     std::unique_ptr<ClientCompositionRequestCache> mClientCompositionRequestCache;
     std::unique_ptr<planner::Planner> mPlanner;
+    std::unique_ptr<HwcAsyncWorker> mHwComposerAsyncWorker;
 };
 
 // This template factory function standardizes the implementation details of the
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
index 66dd825..61be983 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <cstdint>
+#include "aidl/android/hardware/graphics/composer3/DimmingStage.h"
 
 #include <math/mat4.h>
 #include <ui/FenceTime.h>
@@ -37,6 +38,8 @@
 #include <ui/Region.h>
 #include <ui/Transform.h>
 
+#include "DisplayHardware/HWComposer.h"
+
 namespace android {
 
 namespace compositionengine::impl {
@@ -114,6 +117,10 @@
     // Current target dataspace
     ui::Dataspace targetDataspace{ui::Dataspace::UNKNOWN};
 
+    std::optional<android::HWComposer::DeviceRequestedChanges> previousDeviceRequestedChanges{};
+
+    bool previousDeviceRequestedSuccess = false;
+
     // The earliest time to send the present command to the HAL
     std::chrono::steady_clock::time_point earliestPresentTime;
 
@@ -133,10 +140,28 @@
     // Brightness of the client target, normalized to display brightness
     float clientTargetBrightness{1.f};
 
+    // Stage in which the client target should apply dimming
+    aidl::android::hardware::graphics::composer3::DimmingStage clientTargetDimmingStage{
+            aidl::android::hardware::graphics::composer3::DimmingStage::NONE};
+
     // 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;
 
+    enum class CompositionStrategyPredictionState : uint32_t {
+        // Composition strategy prediction did not run for this frame.
+        DISABLED = 0,
+        // Composition strategy predicted successfully for this frame.
+        SUCCESS = 1,
+        // Composition strategy prediction failed for this frame.
+        FAIL = 2,
+
+        ftl_last = FAIL
+    };
+
+    CompositionStrategyPredictionState strategyPrediction =
+            CompositionStrategyPredictionState::DISABLED;
+
     // Debugging
     void dump(std::string& result) const;
 };
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/RenderSurface.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/RenderSurface.h
index a8a5380..e4cb113 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/RenderSurface.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/RenderSurface.h
@@ -63,6 +63,7 @@
     void queueBuffer(base::unique_fd readyFence) override;
     void onPresentDisplayCompleted() override;
     void flip() override;
+    bool supportsCompositionStrategyPrediction() const override;
 
     // Debugging
     void dump(std::string& result) const override;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
index cb00e71..29d3366 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
@@ -16,20 +16,20 @@
 
 #pragma once
 
+#include <string>
+
+#include <aidl/android/hardware/graphics/common/BufferUsage.h>
+#include <aidl/android/hardware/graphics/composer3/Composition.h>
 #include <android-base/strings.h>
+#include <ftl/flags.h>
+#include <math/HashCombine.h>
+
 #include <compositionengine/LayerFE.h>
 #include <compositionengine/LayerFECompositionState.h>
 #include <compositionengine/OutputLayer.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
-#include <ftl/Flags.h>
-
-#include <string>
 
 #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>
@@ -84,13 +84,13 @@
 public:
     virtual ~StateInterface() = default;
 
-    virtual Flags<LayerStateField> update(const compositionengine::OutputLayer* layer) = 0;
+    virtual ftl::Flags<LayerStateField> update(const compositionengine::OutputLayer* layer) = 0;
 
     virtual size_t getHash() const = 0;
 
     virtual LayerStateField getField() const = 0;
 
-    virtual Flags<LayerStateField> getFieldIfDifferent(const StateInterface* other) const = 0;
+    virtual ftl::Flags<LayerStateField> getFieldIfDifferent(const StateInterface* other) const = 0;
 
     virtual bool equals(const StateInterface* other) const = 0;
 
@@ -152,12 +152,12 @@
     ~OutputLayerState() override = default;
 
     // Returns this member's field flag if it was changed
-    Flags<LayerStateField> update(const compositionengine::OutputLayer* layer) override {
+    ftl::Flags<LayerStateField> update(const compositionengine::OutputLayer* layer) override {
         T newValue = mReader(layer);
         return update(newValue);
     }
 
-    Flags<LayerStateField> update(const T& newValue) {
+    ftl::Flags<LayerStateField> update(const T& newValue) {
         if (!mEquals(mValue, newValue)) {
             mValue = newValue;
             mHash = {};
@@ -176,14 +176,14 @@
         return *mHash;
     }
 
-    Flags<LayerStateField> getFieldIfDifferent(const StateInterface* other) const override {
+    ftl::Flags<LayerStateField> getFieldIfDifferent(const StateInterface* other) const override {
         if (other->getField() != FIELD) {
             return {};
         }
 
         // The early return ensures that this downcast is sound
         const OutputLayerState* otherState = static_cast<const OutputLayerState*>(other);
-        return *this != *otherState ? FIELD : Flags<LayerStateField>{};
+        return *this != *otherState ? FIELD : ftl::Flags<LayerStateField>{};
     }
 
     bool equals(const StateInterface* other) const override {
@@ -215,7 +215,7 @@
     LayerState(compositionengine::OutputLayer* layer);
 
     // Returns which fields were updated
-    Flags<LayerStateField> update(compositionengine::OutputLayer*);
+    ftl::Flags<LayerStateField> update(compositionengine::OutputLayer*);
 
     // Computes a hash for this LayerState.
     // The hash is only computed from NonUniqueFields, and excludes GraphicBuffers since they are
@@ -224,7 +224,7 @@
 
     // Returns the bit-set of differing fields between this LayerState and another LayerState.
     // This bit-set is based on NonUniqueFields only, and excludes GraphicBuffers.
-    Flags<LayerStateField> getDifferingFields(const LayerState& other) const;
+    ftl::Flags<LayerStateField> getDifferingFields(const LayerState& other) const;
 
     compositionengine::OutputLayer* getOutputLayer() const { return mOutputLayer; }
     int32_t getId() const { return mId.get(); }
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h
index ef1560e..6be6735 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include <ftl/flags.h>
+
 #include <compositionengine/impl/planner/LayerState.h>
 
 namespace android::compositionengine::impl::planner {
@@ -35,7 +37,7 @@
         // This implies that only one layer is allowed to differ in an approximate match.
         size_t differingIndex;
         // Set of fields that differ for the differing layer in the approximate match.
-        Flags<LayerStateField> differingFields;
+        ftl::Flags<LayerStateField> differingFields;
     };
 
     // Returns an approximate match when comparing this layer stack with the provided list of
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
index d90cc90..72e6f3b 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
@@ -41,6 +41,7 @@
     MOCK_METHOD1(createDisplayColorProfile, void(const DisplayColorProfileCreationArgs&));
     MOCK_METHOD1(createRenderSurface, void(const RenderSurfaceCreationArgs&));
     MOCK_METHOD1(createClientCompositionCache, void(uint32_t));
+    MOCK_METHOD1(setPredictCompositionStrategy, void(bool));
 };
 
 } // namespace android::compositionengine::mock
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
index b68b95d..fa86076 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
@@ -22,6 +22,7 @@
 #include <compositionengine/Output.h>
 #include <compositionengine/OutputLayer.h>
 #include <compositionengine/RenderSurface.h>
+#include <compositionengine/impl/GpuCompositionResult.h>
 #include <compositionengine/impl/OutputCompositionState.h>
 #include <gmock/gmock.h>
 
@@ -99,16 +100,24 @@
     MOCK_METHOD0(beginFrame, void());
 
     MOCK_METHOD0(prepareFrame, void());
-    MOCK_METHOD0(chooseCompositionStrategy, void());
+    MOCK_METHOD1(prepareFrameAsync, GpuCompositionResult(const CompositionRefreshArgs&));
+    MOCK_METHOD1(chooseCompositionStrategy,
+                 bool(std::optional<android::HWComposer::DeviceRequestedChanges>*));
+    MOCK_METHOD1(chooseCompositionStrategyAsync,
+                 std::future<bool>(std::optional<android::HWComposer::DeviceRequestedChanges>*));
+    MOCK_METHOD1(applyCompositionStrategy,
+                 void(const std::optional<android::HWComposer::DeviceRequestedChanges>&));
 
     MOCK_METHOD1(devOptRepaintFlash, void(const compositionengine::CompositionRefreshArgs&));
 
-    MOCK_METHOD1(finishFrame, void(const compositionengine::CompositionRefreshArgs&));
+    MOCK_METHOD2(finishFrame,
+                 void(const compositionengine::CompositionRefreshArgs&, GpuCompositionResult&&));
 
-    MOCK_METHOD2(composeSurfaces,
+    MOCK_METHOD4(composeSurfaces,
                  std::optional<base::unique_fd>(
                          const Region&,
-                         const compositionengine::CompositionRefreshArgs& refreshArgs));
+                         const compositionengine::CompositionRefreshArgs& refreshArgs,
+                         std::shared_ptr<renderengine::ExternalTexture>, base::unique_fd&));
     MOCK_CONST_METHOD0(getSkipColorTransform, bool());
 
     MOCK_METHOD0(postFramebuffer, void());
@@ -121,6 +130,8 @@
                  void(const Region&, std::vector<LayerFE::LayerSettings>&));
     MOCK_METHOD1(setExpensiveRenderingExpected, void(bool));
     MOCK_METHOD1(cacheClientCompositionRequests, void(uint32_t));
+    MOCK_METHOD1(canPredictCompositionStrategy, bool(const CompositionRefreshArgs&));
+    MOCK_METHOD1(setPredictCompositionStrategy, void(bool));
 };
 
 } // namespace android::compositionengine::mock
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/RenderSurface.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/RenderSurface.h
index fe858c2..e12aebb 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/RenderSurface.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/RenderSurface.h
@@ -45,6 +45,7 @@
     MOCK_METHOD0(flip, void());
     MOCK_CONST_METHOD1(dump, void(std::string& result));
     MOCK_CONST_METHOD0(getPageFlipCount, std::uint32_t());
+    MOCK_CONST_METHOD0(supportsCompositionStrategyPrediction, bool());
 };
 
 } // namespace android::compositionengine::mock
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index 2165e1d..b79b46b 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -131,15 +131,11 @@
 }
 
 void Display::dump(std::string& out) const {
-    using android::base::StringAppendF;
+    const char* const type = isVirtual() ? "virtual" : "physical";
+    base::StringAppendF(&out, "Display %s (%s, \"%s\")", to_string(mId).c_str(), type,
+                        getName().c_str());
 
-    StringAppendF(&out, "   Composition Display State: [\"%s\"]", getName().c_str());
-
-    out.append("\n   ");
-    dumpVal(out, "isVirtual", isVirtual());
-    dumpVal(out, "DisplayId", to_string(mId));
-    out.append("\n");
-
+    out.append("\n   Composition Display State:\n");
     Output::dumpBase(out);
 }
 
@@ -207,16 +203,8 @@
     setReleasedLayers(std::move(releasedLayers));
 }
 
-void Display::chooseCompositionStrategy() {
-    ATRACE_CALL();
-    ALOGV(__FUNCTION__);
-
-    if (mIsDisconnected) {
-        return;
-    }
-
-    // Default to the base settings -- client composition only.
-    Output::chooseCompositionStrategy();
+void Display::beginFrame() {
+    Output::beginFrame();
 
     // If we don't have a HWC display, then we are done.
     const auto halDisplayId = HalDisplayId::tryCast(mId);
@@ -224,43 +212,66 @@
         return;
     }
 
-    // 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,
+                                         getState().displayBrightnessNits,
                                          Hwc2::Composer::DisplayBrightnessOptions{
                                                  .applyImmediately = false})
                         .get();
         ALOGE_IF(result != NO_ERROR, "setDisplayBrightness failed for %s: %d, (%s)",
                  getName().c_str(), result, strerror(-result));
     }
+    // Clear out the display brightness now that it's been communicated to composer.
+    editState().displayBrightness.reset();
+}
 
+bool Display::chooseCompositionStrategy(
+        std::optional<android::HWComposer::DeviceRequestedChanges>* outChanges) {
+    ATRACE_CALL();
+    ALOGV(__FUNCTION__);
+
+    if (mIsDisconnected) {
+        return false;
+    }
+
+    // If we don't have a HWC display, then we are done.
+    const auto halDisplayId = HalDisplayId::tryCast(mId);
+    if (!halDisplayId) {
+        return false;
+    }
+
+    // Get any composition changes requested by the HWC device, and apply them.
+    std::optional<android::HWComposer::DeviceRequestedChanges> changes;
+    auto& hwc = getCompositionEngine().getHwComposer();
     if (status_t result =
                 hwc.getDeviceCompositionChanges(*halDisplayId, anyLayersRequireClientComposition(),
                                                 getState().earliestPresentTime,
                                                 getState().previousPresentFence,
-                                                getState().expectedPresentTime, &changes);
+                                                getState().expectedPresentTime, outChanges);
         result != NO_ERROR) {
         ALOGE("chooseCompositionStrategy failed for %s: %d (%s)", getName().c_str(), result,
               strerror(-result));
-        return;
+        return false;
     }
+
+    return true;
+}
+
+void Display::applyCompositionStrategy(const std::optional<DeviceRequestedChanges>& changes) {
     if (changes) {
         applyChangedTypesToLayers(changes->changedTypes);
         applyDisplayRequests(changes->displayRequests);
         applyLayerRequestsToLayers(changes->layerRequests);
-        applyClientTargetRequests(changes->clientTargetProperty, changes->clientTargetBrightness);
+        applyClientTargetRequests(changes->clientTargetProperty);
     }
 
     // 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 {
@@ -273,12 +284,6 @@
     return hwc.hasCapability(Capability::SKIP_CLIENT_COLOR_TRANSFORM);
 }
 
-bool Display::anyLayersRequireClientComposition() const {
-    const auto layers = getOutputLayersOrderedByZ();
-    return std::any_of(layers.begin(), layers.end(),
-                       [](const auto& layer) { return layer->requiresClientComposition(); });
-}
-
 bool Display::allLayersRequireClientComposition() const {
     const auto layers = getOutputLayersOrderedByZ();
     return std::all_of(layers.begin(), layers.end(),
@@ -327,16 +332,19 @@
     }
 }
 
-void Display::applyClientTargetRequests(const ClientTargetProperty& clientTargetProperty,
-                                        float brightness) {
-    if (clientTargetProperty.dataspace == ui::Dataspace::UNKNOWN) {
+void Display::applyClientTargetRequests(const ClientTargetProperty& clientTargetProperty) {
+    if (static_cast<ui::Dataspace>(clientTargetProperty.clientTargetProperty.dataspace) ==
+        ui::Dataspace::UNKNOWN) {
         return;
     }
 
-    editState().dataspace = clientTargetProperty.dataspace;
-    editState().clientTargetBrightness = brightness;
-    getRenderSurface()->setBufferDataspace(clientTargetProperty.dataspace);
-    getRenderSurface()->setBufferPixelFormat(clientTargetProperty.pixelFormat);
+    editState().dataspace =
+            static_cast<ui::Dataspace>(clientTargetProperty.clientTargetProperty.dataspace);
+    editState().clientTargetBrightness = clientTargetProperty.brightness;
+    editState().clientTargetDimmingStage = clientTargetProperty.dimmingStage;
+    getRenderSurface()->setBufferDataspace(editState().dataspace);
+    getRenderSurface()->setBufferPixelFormat(
+            static_cast<ui::PixelFormat>(clientTargetProperty.clientTargetProperty.pixelFormat));
 }
 
 compositionengine::Output::FrameFences Display::presentAndGetFrameFences() {
@@ -376,7 +384,8 @@
     }
 }
 
-void Display::finishFrame(const compositionengine::CompositionRefreshArgs& refreshArgs) {
+void Display::finishFrame(const compositionengine::CompositionRefreshArgs& refreshArgs,
+                          GpuCompositionResult&& result) {
     // We only need to actually compose the display if:
     // 1) It is being handled by hardware composer, which may need this to
     //    keep its virtual display state machine in sync, or
@@ -386,7 +395,7 @@
         return;
     }
 
-    impl::Output::finishFrame(refreshArgs);
+    impl::Output::finishFrame(refreshArgs, std::move(result));
 }
 
 } // namespace android::compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/src/DisplaySurface.cpp b/services/surfaceflinger/CompositionEngine/src/DisplaySurface.cpp
index db6d4f2..28900af 100644
--- a/services/surfaceflinger/CompositionEngine/src/DisplaySurface.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/DisplaySurface.cpp
@@ -20,4 +20,8 @@
 
 DisplaySurface::~DisplaySurface() = default;
 
+bool DisplaySurface::supportsCompositionStrategyPrediction() const {
+    return true;
+}
+
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/src/DumpHelpers.cpp b/services/surfaceflinger/CompositionEngine/src/DumpHelpers.cpp
index 01c368d..290c710 100644
--- a/services/surfaceflinger/CompositionEngine/src/DumpHelpers.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/DumpHelpers.cpp
@@ -89,7 +89,6 @@
 
 void dumpVal(std::string& out, const char* name, const ui::Transform& transform) {
     transform.dump(out, name);
-    out.append(" ");
 }
 
 void dumpVal(std::string& out, const char* name, const mat4& tr) {
@@ -99,7 +98,7 @@
                   "[%0.3f,%0.3f,%0.3f,%0.3f]"
                   "[%0.3f,%0.3f,%0.3f,%0.3f]"
                   "[%0.3f,%0.3f,%0.3f,%0.3f]"
-                  "[%0.3f,%0.3f,%0.3f,%0.3f]]",
+                  "[%0.3f,%0.3f,%0.3f,%0.3f]] ",
                   name,
                   tr[0][0], tr[1][0], tr[2][0], tr[3][0],
                   tr[0][1], tr[1][1], tr[2][1], tr[3][1],
@@ -109,9 +108,9 @@
 }
 
 void dumpVal(std::string& out, const char* name, const StretchEffect& effect) {
-    StringAppendF(&out, "%s={ width =%f, height = %f, vec=(%f, %f), max=(%f, %f) } ", name,
-                  effect.width, effect.height,
-                  effect.vectorX, effect.vectorY, effect.maxAmountX, effect.maxAmountY);
+    StringAppendF(&out, "%s={width=%f, height=%f, vec=(%f, %f), max=(%f, %f)} ", name, effect.width,
+                  effect.height, effect.vectorX, effect.vectorY, effect.maxAmountX,
+                  effect.maxAmountY);
 }
 
 } // namespace android::compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/src/HwcAsyncWorker.cpp b/services/surfaceflinger/CompositionEngine/src/HwcAsyncWorker.cpp
new file mode 100644
index 0000000..6086f0b
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/src/HwcAsyncWorker.cpp
@@ -0,0 +1,71 @@
+/*
+ * 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 <compositionengine/impl/HwcAsyncWorker.h>
+#include <processgroup/sched_policy.h>
+#include <pthread.h>
+#include <sched.h>
+#include <sys/prctl.h>
+#include <sys/resource.h>
+#include <system/thread_defs.h>
+
+#include <android-base/thread_annotations.h>
+#include <cutils/sched_policy.h>
+
+namespace android::compositionengine::impl {
+
+HwcAsyncWorker::HwcAsyncWorker() {
+    mThread = std::thread(&HwcAsyncWorker::run, this);
+    pthread_setname_np(mThread.native_handle(), "HwcAsyncWorker");
+}
+
+HwcAsyncWorker::~HwcAsyncWorker() {
+    {
+        std::scoped_lock lock(mMutex);
+        mDone = true;
+        mCv.notify_all();
+    }
+    if (mThread.joinable()) {
+        mThread.join();
+    }
+}
+std::future<bool> HwcAsyncWorker::send(std::function<bool()> task) {
+    std::unique_lock<std::mutex> lock(mMutex);
+    android::base::ScopedLockAssertion assumeLock(mMutex);
+    mTask = std::packaged_task<bool()>([task = std::move(task)]() { return task(); });
+    mTaskRequested = true;
+    mCv.notify_one();
+    return mTask.get_future();
+}
+
+void HwcAsyncWorker::run() {
+    set_sched_policy(0, SP_FOREGROUND);
+    struct sched_param param = {0};
+    param.sched_priority = 2;
+    sched_setscheduler(gettid(), SCHED_FIFO, &param);
+
+    std::unique_lock<std::mutex> lock(mMutex);
+    android::base::ScopedLockAssertion assumeLock(mMutex);
+    while (!mDone) {
+        mCv.wait(lock);
+        if (mTaskRequested && mTask.valid()) {
+            mTask();
+            mTaskRequested = false;
+        }
+    }
+}
+
+} // namespace android::compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
index ff7d430..6631a27 100644
--- a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
@@ -121,6 +121,7 @@
     dumpVal(out, "isColorspaceAgnostic", isColorspaceAgnostic);
     dumpVal(out, "dataspace", toString(dataspace), dataspace);
     dumpVal(out, "hdr metadata types", hdrMetadata.validTypes);
+    dumpVal(out, "dimming enabled", dimmingEnabled);
     dumpVal(out, "colorTransform", colorTransform);
 
     out.append("\n");
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 4e67a63..ec86731 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -22,6 +22,7 @@
 #include <compositionengine/LayerFE.h>
 #include <compositionengine/LayerFECompositionState.h>
 #include <compositionengine/RenderSurface.h>
+#include <compositionengine/impl/HwcAsyncWorker.h>
 #include <compositionengine/impl/Output.h>
 #include <compositionengine/impl/OutputCompositionState.h>
 #include <compositionengine/impl/OutputLayer.h>
@@ -57,7 +58,8 @@
 Output::~Output() = default;
 
 namespace impl {
-
+using CompositionStrategyPredictionState =
+        OutputCompositionState::CompositionStrategyPredictionState;
 namespace {
 
 template <typename T>
@@ -292,17 +294,15 @@
 }
 
 void Output::dump(std::string& out) const {
-    using android::base::StringAppendF;
-
-    StringAppendF(&out, "   Composition Output State: [\"%s\"]", mName.c_str());
-
-    out.append("\n   ");
+    base::StringAppendF(&out, "Output \"%s\"", mName.c_str());
+    out.append("\n   Composition Output State:\n");
 
     dumpBase(out);
 }
 
 void Output::dumpBase(std::string& out) const {
     dumpState(out);
+    out += '\n';
 
     if (mDisplayColorProfile) {
         mDisplayColorProfile->dump(out);
@@ -310,13 +310,15 @@
         out.append("    No display color profile!\n");
     }
 
+    out += '\n';
+
     if (mRenderSurface) {
         mRenderSurface->dump(out);
     } else {
         out.append("    No render surface!\n");
     }
 
-    android::base::StringAppendF(&out, "\n   %zu Layers\n", getOutputLayerCount());
+    base::StringAppendF(&out, "\n   %zu Layers\n", getOutputLayerCount());
     for (const auto* outputLayer : getOutputLayersOrderedByZ()) {
         if (!outputLayer) {
             continue;
@@ -327,7 +329,7 @@
 
 void Output::dumpPlannerInfo(const Vector<String16>& args, std::string& out) const {
     if (!mPlanner) {
-        base::StringAppendF(&out, "Planner is disabled\n");
+        out.append("Planner is disabled\n");
         return;
     }
     base::StringAppendF(&out, "Planner info for display [%s]\n", mName.c_str());
@@ -434,9 +436,17 @@
     writeCompositionState(refreshArgs);
     setColorTransform(refreshArgs);
     beginFrame();
-    prepareFrame();
+
+    GpuCompositionResult result;
+    const bool predictCompositionStrategy = canPredictCompositionStrategy(refreshArgs);
+    if (predictCompositionStrategy) {
+        result = prepareFrameAsync(refreshArgs);
+    } else {
+        prepareFrame();
+    }
+
     devOptRepaintFlash(refreshArgs);
-    finishFrame(refreshArgs);
+    finishFrame(refreshArgs, std::move(result));
     postFramebuffer();
     renderCachedSets(refreshArgs);
 }
@@ -953,19 +963,74 @@
     ATRACE_CALL();
     ALOGV(__FUNCTION__);
 
-    const auto& outputState = getState();
+    auto& outputState = editState();
     if (!outputState.isEnabled) {
         return;
     }
 
-    chooseCompositionStrategy();
+    std::optional<android::HWComposer::DeviceRequestedChanges> changes;
+    bool success = chooseCompositionStrategy(&changes);
+    resetCompositionStrategy();
+    outputState.strategyPrediction = CompositionStrategyPredictionState::DISABLED;
+    outputState.previousDeviceRequestedChanges = changes;
+    outputState.previousDeviceRequestedSuccess = success;
+    if (success) {
+        applyCompositionStrategy(changes);
+    }
+    finishPrepareFrame();
+}
 
-    if (mPlanner) {
-        mPlanner->reportFinalPlan(getOutputLayersOrderedByZ());
+std::future<bool> Output::chooseCompositionStrategyAsync(
+        std::optional<android::HWComposer::DeviceRequestedChanges>* changes) {
+    return mHwComposerAsyncWorker->send(
+            [&, changes]() { return chooseCompositionStrategy(changes); });
+}
+
+GpuCompositionResult Output::prepareFrameAsync(const CompositionRefreshArgs& refreshArgs) {
+    ATRACE_CALL();
+    ALOGV(__FUNCTION__);
+    auto& state = editState();
+    const auto& previousChanges = state.previousDeviceRequestedChanges;
+    std::optional<android::HWComposer::DeviceRequestedChanges> changes;
+    resetCompositionStrategy();
+    auto hwcResult = chooseCompositionStrategyAsync(&changes);
+    if (state.previousDeviceRequestedSuccess) {
+        applyCompositionStrategy(previousChanges);
+    }
+    finishPrepareFrame();
+
+    base::unique_fd bufferFence;
+    std::shared_ptr<renderengine::ExternalTexture> buffer;
+    updateProtectedContentState();
+    const bool dequeueSucceeded = dequeueRenderBuffer(&bufferFence, &buffer);
+    GpuCompositionResult compositionResult;
+    if (dequeueSucceeded) {
+        std::optional<base::unique_fd> optFd =
+                composeSurfaces(Region::INVALID_REGION, refreshArgs, buffer, bufferFence);
+        if (optFd) {
+            compositionResult.fence = std::move(*optFd);
+        }
     }
 
-    mRenderSurface->prepareFrame(outputState.usesClientComposition,
-                                 outputState.usesDeviceComposition);
+    auto chooseCompositionSuccess = hwcResult.get();
+    const bool predictionSucceeded = dequeueSucceeded && changes == previousChanges;
+    state.strategyPrediction = predictionSucceeded ? CompositionStrategyPredictionState::SUCCESS
+                                                   : CompositionStrategyPredictionState::FAIL;
+    if (!predictionSucceeded) {
+        ATRACE_NAME("CompositionStrategyPredictionMiss");
+        resetCompositionStrategy();
+        if (chooseCompositionSuccess) {
+            applyCompositionStrategy(changes);
+        }
+        finishPrepareFrame();
+        // Track the dequeued buffer to reuse so we don't need to dequeue another one.
+        compositionResult.buffer = buffer;
+    } else {
+        ATRACE_NAME("CompositionStrategyPredictionHit");
+    }
+    state.previousDeviceRequestedChanges = std::move(changes);
+    state.previousDeviceRequestedSuccess = chooseCompositionSuccess;
+    return compositionResult;
 }
 
 void Output::devOptRepaintFlash(const compositionengine::CompositionRefreshArgs& refreshArgs) {
@@ -975,7 +1040,11 @@
 
     if (getState().isEnabled) {
         if (const auto dirtyRegion = getDirtyRegion(); !dirtyRegion.isEmpty()) {
-            static_cast<void>(composeSurfaces(dirtyRegion, refreshArgs));
+            base::unique_fd bufferFence;
+            std::shared_ptr<renderengine::ExternalTexture> buffer;
+            updateProtectedContentState();
+            dequeueRenderBuffer(&bufferFence, &buffer);
+            static_cast<void>(composeSurfaces(dirtyRegion, refreshArgs, buffer, bufferFence));
             mRenderSurface->queueBuffer(base::unique_fd());
         }
     }
@@ -987,17 +1056,33 @@
     prepareFrame();
 }
 
-void Output::finishFrame(const compositionengine::CompositionRefreshArgs& refreshArgs) {
+void Output::finishFrame(const CompositionRefreshArgs& refreshArgs, GpuCompositionResult&& result) {
     ATRACE_CALL();
     ALOGV(__FUNCTION__);
-
-    if (!getState().isEnabled) {
+    const auto& outputState = getState();
+    if (!outputState.isEnabled) {
         return;
     }
 
-    // Repaint the framebuffer (if needed), getting the optional fence for when
-    // the composition completes.
-    auto optReadyFence = composeSurfaces(Region::INVALID_REGION, refreshArgs);
+    std::optional<base::unique_fd> optReadyFence;
+    std::shared_ptr<renderengine::ExternalTexture> buffer;
+    base::unique_fd bufferFence;
+    if (outputState.strategyPrediction == CompositionStrategyPredictionState::SUCCESS) {
+        optReadyFence = std::move(result.fence);
+    } else {
+        if (result.bufferAvailable()) {
+            buffer = std::move(result.buffer);
+            bufferFence = std::move(result.fence);
+        } else {
+            updateProtectedContentState();
+            if (!dequeueRenderBuffer(&bufferFence, &buffer)) {
+                return;
+            }
+        }
+        // Repaint the framebuffer (if needed), getting the optional fence for when
+        // the composition completes.
+        optReadyFence = composeSurfaces(Region::INVALID_REGION, refreshArgs, buffer, bufferFence);
+    }
     if (!optReadyFence) {
         return;
     }
@@ -1006,16 +1091,8 @@
     mRenderSurface->queueBuffer(std::move(*optReadyFence));
 }
 
-std::optional<base::unique_fd> Output::composeSurfaces(
-        const Region& debugRegion, const compositionengine::CompositionRefreshArgs& refreshArgs) {
-    ATRACE_CALL();
-    ALOGV(__FUNCTION__);
-
+void Output::updateProtectedContentState() {
     const auto& outputState = getState();
-    OutputCompositionState& outputCompositionState = editState();
-    const TracedOrdinal<bool> hasClientComposition = {"hasClientComposition",
-                                                      outputState.usesClientComposition};
-
     auto& renderEngine = getCompositionEngine().getRenderEngine();
     const bool supportsProtectedContent = renderEngine.supportsProtectedContent();
 
@@ -1037,29 +1114,48 @@
     } else if (!outputState.isSecure && renderEngine.isProtected()) {
         renderEngine.useProtectedContext(false);
     }
+}
 
-    base::unique_fd fd;
-
-    std::shared_ptr<renderengine::ExternalTexture> tex;
+bool Output::dequeueRenderBuffer(base::unique_fd* bufferFence,
+                                 std::shared_ptr<renderengine::ExternalTexture>* tex) {
+    const auto& outputState = getState();
 
     // If we aren't doing client composition on this output, but do have a
     // flipClientTarget request for this frame on this output, we still need to
     // dequeue a buffer.
-    if (hasClientComposition || outputState.flipClientTarget) {
-        tex = mRenderSurface->dequeueBuffer(&fd);
-        if (tex == nullptr) {
+    if (outputState.usesClientComposition || outputState.flipClientTarget) {
+        *tex = mRenderSurface->dequeueBuffer(bufferFence);
+        if (*tex == nullptr) {
             ALOGW("Dequeuing buffer for display [%s] failed, bailing out of "
                   "client composition for this frame",
                   mName.c_str());
-            return {};
+            return false;
         }
     }
+    return true;
+}
 
+std::optional<base::unique_fd> Output::composeSurfaces(
+        const Region& debugRegion, const compositionengine::CompositionRefreshArgs& refreshArgs,
+        std::shared_ptr<renderengine::ExternalTexture> tex, base::unique_fd& fd) {
+    ATRACE_CALL();
+    ALOGV(__FUNCTION__);
+
+    const auto& outputState = getState();
+    const TracedOrdinal<bool> hasClientComposition = {"hasClientComposition",
+                                                      outputState.usesClientComposition};
     if (!hasClientComposition) {
         setExpensiveRenderingExpected(false);
         return base::unique_fd();
     }
 
+    if (tex == nullptr) {
+        ALOGW("Buffer not valid for display [%s], bailing out of "
+              "client composition for this frame",
+              mName.c_str());
+        return {};
+    }
+
     ALOGV("hasClientComposition");
 
     renderengine::DisplaySettings clientCompositionDisplay;
@@ -1080,6 +1176,10 @@
             mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance();
     clientCompositionDisplay.targetLuminanceNits =
             outputState.clientTargetBrightness * outputState.displayBrightnessNits;
+    clientCompositionDisplay.dimmingStage = outputState.clientTargetDimmingStage;
+    clientCompositionDisplay.renderIntent =
+            static_cast<aidl::android::hardware::graphics::composer3::RenderIntent>(
+                    outputState.renderIntent);
 
     // Compute the global color transform matrix.
     clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix;
@@ -1087,6 +1187,8 @@
             outputState.usesDeviceComposition || getSkipColorTransform();
 
     // Generate the client composition requests for the layers on this output.
+    auto& renderEngine = getCompositionEngine().getRenderEngine();
+    const bool supportsProtectedContent = renderEngine.supportsProtectedContent();
     std::vector<LayerFE*> clientCompositionLayersFE;
     std::vector<LayerFE::LayerSettings> clientCompositionLayers =
             generateClientCompositionRequests(supportsProtectedContent,
@@ -1094,16 +1196,19 @@
                                               clientCompositionLayersFE);
     appendRegionFlashRequests(debugRegion, clientCompositionLayers);
 
+    OutputCompositionState& outputCompositionState = editState();
     // Check if the client composition requests were rendered into the provided graphic buffer. If
     // so, we can reuse the buffer and avoid client composition.
     if (mClientCompositionRequestCache) {
         if (mClientCompositionRequestCache->exists(tex->getBuffer()->getId(),
                                                    clientCompositionDisplay,
                                                    clientCompositionLayers)) {
+            ATRACE_NAME("ClientCompositionCacheHit");
             outputCompositionState.reusedClientComposition = true;
             setExpensiveRenderingExpected(false);
             return base::unique_fd();
         }
+        ATRACE_NAME("ClientCompositionCacheMiss");
         mClientCompositionRequestCache->add(tex->getBuffer()->getId(), clientCompositionDisplay,
                                             clientCompositionLayers);
     }
@@ -1355,7 +1460,7 @@
     outputState.dirtyRegion.set(outputState.displaySpace.getBoundsAsRect());
 }
 
-void Output::chooseCompositionStrategy() {
+void Output::resetCompositionStrategy() {
     // The base output implementation can only do client composition
     auto& outputState = editState();
     outputState.usesClientComposition = true;
@@ -1375,5 +1480,63 @@
     return result;
 }
 
+void Output::setPredictCompositionStrategy(bool predict) {
+    if (predict) {
+        mHwComposerAsyncWorker = std::make_unique<HwcAsyncWorker>();
+    } else {
+        mHwComposerAsyncWorker.reset(nullptr);
+    }
+}
+
+bool Output::canPredictCompositionStrategy(const CompositionRefreshArgs& refreshArgs) {
+    if (!getState().isEnabled || !mHwComposerAsyncWorker) {
+        ALOGV("canPredictCompositionStrategy disabled");
+        return false;
+    }
+
+    if (!getState().previousDeviceRequestedChanges) {
+        ALOGV("canPredictCompositionStrategy previous changes not available");
+        return false;
+    }
+
+    if (!mRenderSurface->supportsCompositionStrategyPrediction()) {
+        ALOGV("canPredictCompositionStrategy surface does not support");
+        return false;
+    }
+
+    if (refreshArgs.devOptFlashDirtyRegionsDelay) {
+        ALOGV("canPredictCompositionStrategy devOptFlashDirtyRegionsDelay");
+        return false;
+    }
+
+    // If no layer uses clientComposition, then don't predict composition strategy
+    // because we have less work to do in parallel.
+    if (!anyLayersRequireClientComposition()) {
+        ALOGV("canPredictCompositionStrategy no layer uses clientComposition");
+        return false;
+    }
+
+    if (!refreshArgs.updatingOutputGeometryThisFrame) {
+        return true;
+    }
+
+    ALOGV("canPredictCompositionStrategy updatingOutputGeometryThisFrame");
+    return false;
+}
+
+bool Output::anyLayersRequireClientComposition() const {
+    const auto layers = getOutputLayersOrderedByZ();
+    return std::any_of(layers.begin(), layers.end(),
+                       [](const auto& layer) { return layer->requiresClientComposition(); });
+}
+
+void Output::finishPrepareFrame() {
+    const auto& state = getState();
+    if (mPlanner) {
+        mPlanner->reportFinalPlan(getOutputLayersOrderedByZ());
+    }
+    mRenderSurface->prepareFrame(state.usesClientComposition, state.usesDeviceComposition);
+}
+
 } // namespace impl
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
index 482250a..3b85e3b 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <ftl/enum.h>
+
 #include <compositionengine/impl/DumpHelpers.h>
 #include <compositionengine/impl/OutputCompositionState.h>
 
@@ -23,18 +25,19 @@
     out.append("   ");
     dumpVal(out, "isEnabled", isEnabled);
     dumpVal(out, "isSecure", isSecure);
-
-    dumpVal(out, "usesClientComposition", usesClientComposition);
     dumpVal(out, "usesDeviceComposition", usesDeviceComposition);
+
+    out.append("\n   ");
+    dumpVal(out, "usesClientComposition", usesClientComposition);
     dumpVal(out, "flipClientTarget", flipClientTarget);
     dumpVal(out, "reusedClientComposition", reusedClientComposition);
-    dumpVal(out, "layerFilter", layerFilter);
 
     out.append("\n   ");
-
+    dumpVal(out, "layerFilter", layerFilter);
+    out.append("\n   ");
     dumpVal(out, "transform", transform);
 
-    out.append("\n   ");
+    out.append("   "); // ui::Transform::dump appends EOL.
     dumpVal(out, "layerStackSpace", to_string(layerStackSpace));
     out.append("\n   ");
     dumpVal(out, "framebufferSpace", to_string(framebufferSpace));
@@ -46,18 +49,24 @@
     dumpVal(out, "needsFiltering", needsFiltering);
 
     out.append("\n   ");
-
     dumpVal(out, "colorMode", toString(colorMode), colorMode);
     dumpVal(out, "renderIntent", toString(renderIntent), renderIntent);
     dumpVal(out, "dataspace", toString(dataspace), dataspace);
+    dumpVal(out, "targetDataspace", toString(targetDataspace), targetDataspace);
+
+    out.append("\n   ");
     dumpVal(out, "colorTransformMatrix", colorTransformMatrix);
-    dumpVal(out, "target dataspace", toString(targetDataspace), targetDataspace);
+
+    out.append("\n   ");
     dumpVal(out, "displayBrightnessNits", displayBrightnessNits);
     dumpVal(out, "sdrWhitePointNits", sdrWhitePointNits);
     dumpVal(out, "clientTargetBrightness", clientTargetBrightness);
     dumpVal(out, "displayBrightness", displayBrightness);
 
-    out.append("\n");
+    out.append("\n   ");
+    dumpVal(out, "compositionStrategyPredictionState", ftl::enum_string(strategyPrediction));
+
+    out += '\n';
 }
 
 } // namespace android::compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index 723593d..3289d55 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -324,9 +324,10 @@
 
     // For hdr content, treat the white point as the display brightness - HDR content should not be
     // boosted or dimmed.
+    // If the layer explicitly requests to disable dimming, then don't dim either.
     if (isHdrDataspace(state.dataspace) ||
         getOutput().getState().displayBrightnessNits == getOutput().getState().sdrWhitePointNits ||
-        getOutput().getState().displayBrightnessNits == 0.f) {
+        getOutput().getState().displayBrightnessNits == 0.f || !layerFEState->dimmingEnabled) {
         state.dimmingRatio = 1.f;
         state.whitePointNits = getOutput().getState().displayBrightnessNits;
     } else {
diff --git a/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp b/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
index 12c2c8e..5a3af7b 100644
--- a/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
@@ -289,5 +289,9 @@
     return mTexture;
 }
 
+bool RenderSurface::supportsCompositionStrategyPrediction() const {
+    return mDisplaySurface->supportsCompositionStrategyPrediction();
+}
+
 } // namespace impl
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
index c79ca0d..f439caf 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
@@ -41,7 +41,7 @@
     update(layer);
 }
 
-Flags<LayerStateField> LayerState::update(compositionengine::OutputLayer* layer) {
+ftl::Flags<LayerStateField> LayerState::update(compositionengine::OutputLayer* layer) {
     ALOGE_IF(mOutputLayer != layer && layer->getLayerFE().getSequence() != mId.get(),
              "[%s] Expected mOutputLayer ID to never change: %d, %d", __func__,
              layer->getLayerFE().getSequence(), mId.get());
@@ -50,7 +50,7 @@
     // same, i.e., the LayerFE is the same. An example use-case is screen rotation.
     mOutputLayer = layer;
 
-    Flags<LayerStateField> differences;
+    ftl::Flags<LayerStateField> differences;
 
     // Update the unique fields as well, since we have to set them at least
     // once from the OutputLayer
@@ -76,8 +76,8 @@
     return hash;
 }
 
-Flags<LayerStateField> LayerState::getDifferingFields(const LayerState& other) const {
-    Flags<LayerStateField> differences;
+ftl::Flags<LayerStateField> LayerState::getDifferingFields(const LayerState& other) const {
+    ftl::Flags<LayerStateField> differences;
     auto myFields = getNonUniqueFields();
     auto otherFields = other.getNonUniqueFields();
     for (size_t i = 0; i < myFields.size(); ++i) {
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
index 74d2701..c8413eb 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
@@ -97,7 +97,7 @@
         if (const auto layerEntry = mPreviousLayers.find(id); layerEntry != mPreviousLayers.end()) {
             // Track changes from previous info
             LayerState& state = layerEntry->second;
-            Flags<LayerStateField> differences = state.update(layer);
+            ftl::Flags<LayerStateField> differences = state.update(layer);
             if (differences.get() == 0) {
                 state.incrementFramesSinceBufferUpdate();
             } else {
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp
index 2d53583..2fc029f 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp
@@ -57,7 +57,7 @@
             return std::nullopt;
         }
 
-        Flags<LayerStateField> differingFields = mLayers[i].getDifferingFields(*other[i]);
+        ftl::Flags<LayerStateField> differingFields = mLayers[i].getDifferingFields(*other[i]);
 
         // If we don't find an approximate match on this layer, then the LayerStacks differ
         // by too much, so return nothing
diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
index 5cc0f97..0e5a7b6 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
@@ -30,7 +30,9 @@
 #include <compositionengine/mock/OutputLayer.h>
 #include <compositionengine/mock/RenderSurface.h>
 #include <gtest/gtest.h>
+#include <renderengine/mock/FakeExternalTexture.h>
 #include <renderengine/mock/RenderEngine.h>
+
 #include <ui/Rect.h>
 #include <ui/StaticDisplayInfo.h>
 
@@ -43,6 +45,7 @@
 
 using aidl::android::hardware::graphics::composer3::Capability;
 using aidl::android::hardware::graphics::composer3::Composition;
+using aidl::android::hardware::graphics::composer3::DimmingStage;
 
 namespace android::compositionengine {
 namespace {
@@ -113,8 +116,9 @@
             return mCompositionEngine;
         };
 
+        size_t getOutputLayerCount() const override { return 1u; }
+
         // Mock implementation overrides
-        MOCK_CONST_METHOD0(getOutputLayerCount, size_t());
         MOCK_CONST_METHOD1(getOutputLayerOrderedByZByIndex,
                            compositionengine::OutputLayer*(size_t));
         MOCK_METHOD2(ensureOutputLayer,
@@ -197,6 +201,26 @@
     std::shared_ptr<Display> mDisplay =
             createPartialMockDisplay<Display>(mCompositionEngine,
                                               getDisplayCreationArgsForPhysicalDisplay());
+
+    android::HWComposer::DeviceRequestedChanges mDeviceRequestedChanges{
+            {{nullptr, Composition::CLIENT}},
+            hal::DisplayRequest::FLIP_CLIENT_TARGET,
+            {{nullptr, hal::LayerRequest::CLEAR_CLIENT_TARGET}},
+            {DEFAULT_DISPLAY_ID.value,
+             {aidl::android::hardware::graphics::common::PixelFormat::RGBA_8888,
+              aidl::android::hardware::graphics::common::Dataspace::UNKNOWN},
+             -1.f,
+             DimmingStage::NONE},
+    };
+
+    void chooseCompositionStrategy(Display* display) {
+        std::optional<android::HWComposer::DeviceRequestedChanges> changes;
+        bool success = display->chooseCompositionStrategy(&changes);
+        display->resetCompositionStrategy();
+        if (success) {
+            display->applyCompositionStrategy(changes);
+        }
+    }
 };
 
 struct FullDisplayImplTestCommon : public DisplayTestCommon {
@@ -213,6 +237,11 @@
                 std::unique_ptr<compositionengine::OutputLayer>(mLayer2.outputLayer));
         mDisplay->injectOutputLayerForTest(
                 std::unique_ptr<compositionengine::OutputLayer>(mLayer3.outputLayer));
+        mResultWithBuffer.buffer = std::make_shared<
+                renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                         1ULL /* bufferId */,
+                                                         HAL_PIXEL_FORMAT_RGBA_8888,
+                                                         0ULL /*usage*/);
     }
 
     Layer mLayer1;
@@ -221,6 +250,8 @@
     StrictMock<HWC2::mock::Layer> hwc2LayerUnknown;
     std::shared_ptr<Display> mDisplay =
             createDisplay<Display>(mCompositionEngine, getDisplayCreationArgsForPhysicalDisplay());
+    impl::GpuCompositionResult mResultWithBuffer;
+    impl::GpuCompositionResult mResultWithoutBuffer;
 };
 
 /*
@@ -553,7 +584,7 @@
             createPartialMockDisplay<Display>(mCompositionEngine, args);
     EXPECT_TRUE(GpuVirtualDisplayId::tryCast(gpuDisplay->getId()));
 
-    gpuDisplay->chooseCompositionStrategy();
+    chooseCompositionStrategy(gpuDisplay.get());
 
     auto& state = gpuDisplay->getState();
     EXPECT_TRUE(state.usesClientComposition);
@@ -566,11 +597,12 @@
                 getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), false, _, _, _, _))
             .WillOnce(Return(INVALID_OPERATION));
 
-    mDisplay->chooseCompositionStrategy();
+    chooseCompositionStrategy(mDisplay.get());
 
     auto& state = mDisplay->getState();
     EXPECT_TRUE(state.usesClientComposition);
     EXPECT_FALSE(state.usesDeviceComposition);
+    EXPECT_FALSE(state.previousDeviceRequestedChanges.has_value());
 }
 
 TEST_F(DisplayChooseCompositionStrategyTest, normalOperation) {
@@ -587,10 +619,16 @@
 
     EXPECT_CALL(mHwComposer,
                 getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _, _))
-            .WillOnce(Return(NO_ERROR));
+            .WillOnce(testing::DoAll(testing::SetArgPointee<5>(mDeviceRequestedChanges),
+                                     Return(NO_ERROR)));
+    EXPECT_CALL(*mDisplay, applyChangedTypesToLayers(mDeviceRequestedChanges.changedTypes))
+            .Times(1);
+    EXPECT_CALL(*mDisplay, applyDisplayRequests(mDeviceRequestedChanges.displayRequests)).Times(1);
+    EXPECT_CALL(*mDisplay, applyLayerRequestsToLayers(mDeviceRequestedChanges.layerRequests))
+            .Times(1);
     EXPECT_CALL(*mDisplay, allLayersRequireClientComposition()).WillOnce(Return(false));
 
-    mDisplay->chooseCompositionStrategy();
+    chooseCompositionStrategy(mDisplay.get());
 
     auto& state = mDisplay->getState();
     EXPECT_FALSE(state.usesClientComposition);
@@ -602,42 +640,25 @@
     // 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));
+    constexpr float kDisplayBrightnessNits = 200.f;
     EXPECT_CALL(mHwComposer,
-                setDisplayBrightness(DEFAULT_DISPLAY_ID, kDisplayBrightness,
+                setDisplayBrightness(DEFAULT_DISPLAY_ID, kDisplayBrightness, kDisplayBrightnessNits,
                                      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();
+    mock::RenderSurface* renderSurface = new StrictMock<mock::RenderSurface>();
+    EXPECT_CALL(*renderSurface, beginFrame(_)).Times(1);
+    mDisplay->setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(renderSurface));
+    mDisplay->editState().displayBrightnessNits = kDisplayBrightnessNits;
+    mDisplay->beginFrame();
 
     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, 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
     // values, use a Sequence to control the matching so the values are returned in a known
     // order.
@@ -651,13 +672,15 @@
 
     EXPECT_CALL(mHwComposer,
                 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);
+            .WillOnce(DoAll(SetArgPointee<5>(mDeviceRequestedChanges), Return(NO_ERROR)));
+    EXPECT_CALL(*mDisplay, applyChangedTypesToLayers(mDeviceRequestedChanges.changedTypes))
+            .Times(1);
+    EXPECT_CALL(*mDisplay, applyDisplayRequests(mDeviceRequestedChanges.displayRequests)).Times(1);
+    EXPECT_CALL(*mDisplay, applyLayerRequestsToLayers(mDeviceRequestedChanges.layerRequests))
+            .Times(1);
     EXPECT_CALL(*mDisplay, allLayersRequireClientComposition()).WillOnce(Return(false));
 
-    mDisplay->chooseCompositionStrategy();
+    chooseCompositionStrategy(mDisplay.get());
 
     auto& state = mDisplay->getState();
     EXPECT_FALSE(state.usesClientComposition);
@@ -822,23 +845,37 @@
 using DisplayApplyClientTargetRequests = DisplayWithLayersTestCommon;
 
 TEST_F(DisplayApplyLayerRequestsToLayersTest, applyClientTargetRequests) {
-    Display::ClientTargetProperty clientTargetProperty = {
-            .pixelFormat = hal::PixelFormat::RGB_565,
-            .dataspace = hal::Dataspace::STANDARD_BT470M,
-    };
-
     static constexpr float kWhitePointNits = 800.f;
 
+    Display::ClientTargetProperty clientTargetProperty = {
+            .clientTargetProperty =
+                    {
+                            .pixelFormat =
+                                    aidl::android::hardware::graphics::common::PixelFormat::RGB_565,
+                            .dataspace = aidl::android::hardware::graphics::common::Dataspace::
+                                    STANDARD_BT470M,
+                    },
+            .brightness = kWhitePointNits,
+            .dimmingStage = aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF,
+    };
+
     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, kWhitePointNits);
+    EXPECT_CALL(*renderSurface,
+                setBufferPixelFormat(static_cast<ui::PixelFormat>(
+                        clientTargetProperty.clientTargetProperty.pixelFormat)));
+    EXPECT_CALL(*renderSurface,
+                setBufferDataspace(static_cast<ui::Dataspace>(
+                        clientTargetProperty.clientTargetProperty.dataspace)));
+    mDisplay->applyClientTargetRequests(clientTargetProperty);
 
     auto& state = mDisplay->getState();
-    EXPECT_EQ(clientTargetProperty.dataspace, state.dataspace);
+    EXPECT_EQ(clientTargetProperty.clientTargetProperty.dataspace,
+              static_cast<aidl::android::hardware::graphics::common::Dataspace>(state.dataspace));
     EXPECT_EQ(kWhitePointNits, state.clientTargetBrightness);
+    EXPECT_EQ(aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF,
+              state.clientTargetDimmingStage);
 }
 
 /*
@@ -921,7 +958,7 @@
     mDisplay->editState().layerStackSpace.setContent(Rect(0, 0, 1, 1));
     mDisplay->editState().dirtyRegion = Region::INVALID_REGION;
 
-    mDisplay->finishFrame({});
+    mDisplay->finishFrame({}, std::move(mResultWithBuffer));
 }
 
 TEST_F(DisplayFinishFrameTest, skipsCompositionIfNotDirty) {
@@ -939,7 +976,7 @@
     gpuDisplay->editState().layerStackSpace.setContent(Rect(0, 0, 1, 1));
     gpuDisplay->editState().dirtyRegion = Region::INVALID_REGION;
 
-    gpuDisplay->finishFrame({});
+    gpuDisplay->finishFrame({}, std::move(mResultWithoutBuffer));
 }
 
 TEST_F(DisplayFinishFrameTest, performsCompositionIfDirty) {
@@ -956,7 +993,7 @@
     gpuDisplay->editState().usesClientComposition = false;
     gpuDisplay->editState().layerStackSpace.setContent(Rect(0, 0, 1, 1));
     gpuDisplay->editState().dirtyRegion = Region(Rect(0, 0, 1, 1));
-    gpuDisplay->finishFrame({});
+    gpuDisplay->finishFrame({}, std::move(mResultWithBuffer));
 }
 
 /*
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index af013b0..ff2aa15 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -84,8 +84,8 @@
     MOCK_METHOD4(setDisplayContentSamplingEnabled, status_t(HalDisplayId, bool, uint8_t, uint64_t));
     MOCK_METHOD4(getDisplayedContentSample,
                  status_t(HalDisplayId, uint64_t, uint64_t, DisplayedFrameStats*));
-    MOCK_METHOD3(setDisplayBrightness,
-                 std::future<status_t>(PhysicalDisplayId, float,
+    MOCK_METHOD4(setDisplayBrightness,
+                 std::future<status_t>(PhysicalDisplayId, float, float,
                                        const Hwc2::Composer::DisplayBrightnessOptions&));
     MOCK_METHOD2(getDisplayBrightnessSupport, status_t(PhysicalDisplayId, bool*));
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index 8eb1946..ceee48c 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -668,6 +668,13 @@
     EXPECT_EQ(mOutputState.sdrWhitePointNits / mOutputState.displayBrightnessNits,
               mOutputLayer.getState().dimmingRatio);
 
+    mLayerFEState.dimmingEnabled = false;
+    mOutputLayer.updateCompositionState(false, false, ui::Transform::RotationFlags::ROT_0);
+    EXPECT_EQ(mOutputState.displayBrightnessNits, mOutputLayer.getState().whitePointNits);
+    EXPECT_EQ(1.f, mOutputLayer.getState().dimmingRatio);
+
+    // change dimmingEnabled back to true.
+    mLayerFEState.dimmingEnabled = true;
     mLayerFEState.dataspace = ui::Dataspace::BT2020_ITU_PQ;
     mLayerFEState.isColorspaceAgnostic = false;
     mOutputLayer.updateCompositionState(false, false, ui::Transform::RotationFlags::ROT_0);
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index dd3858b..42c8b37 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -28,6 +28,7 @@
 #include <gtest/gtest.h>
 #include <renderengine/ExternalTexture.h>
 #include <renderengine/impl/ExternalTexture.h>
+#include <renderengine/mock/FakeExternalTexture.h>
 #include <renderengine/mock/RenderEngine.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
@@ -73,6 +74,9 @@
 constexpr OutputColorSetting kVendorSpecifiedOutputColorSetting =
         static_cast<OutputColorSetting>(0x100);
 
+using CompositionStrategyPredictionState = android::compositionengine::impl::
+        OutputCompositionState::CompositionStrategyPredictionState;
+
 struct OutputPartialMockBase : public impl::Output {
     // compositionengine::Output overrides
     const OutputCompositionState& getState() const override { return mState; }
@@ -989,7 +993,9 @@
     struct OutputPartialMock : public OutputPartialMockBase {
         // Sets up the helper functions called by the function under test to use
         // mock implementations.
-        MOCK_METHOD0(chooseCompositionStrategy, void());
+        MOCK_METHOD1(chooseCompositionStrategy,
+                     bool(std::optional<android::HWComposer::DeviceRequestedChanges>*));
+        MOCK_METHOD0(resetCompositionStrategy, void());
     };
 
     OutputPrepareFrameTest() {
@@ -1015,11 +1021,13 @@
     mOutput.editState().usesClientComposition = false;
     mOutput.editState().usesDeviceComposition = true;
 
-    EXPECT_CALL(mOutput, chooseCompositionStrategy()).Times(1);
+    EXPECT_CALL(mOutput, chooseCompositionStrategy(_)).WillRepeatedly(Return(true));
+    EXPECT_CALL(mOutput, resetCompositionStrategy()).Times(1);
     EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(0u));
     EXPECT_CALL(*mRenderSurface, prepareFrame(false, true));
 
     mOutput.prepareFrame();
+    EXPECT_EQ(mOutput.getState().strategyPrediction, CompositionStrategyPredictionState::DISABLED);
 }
 
 // Note: Use OutputTest and not OutputPrepareFrameTest, so the real
@@ -1035,6 +1043,148 @@
 
     EXPECT_TRUE(mOutput->getState().usesClientComposition);
     EXPECT_FALSE(mOutput->getState().usesDeviceComposition);
+    EXPECT_EQ(mOutput->getState().strategyPrediction, CompositionStrategyPredictionState::DISABLED);
+}
+
+struct OutputPrepareFrameAsyncTest : public testing::Test {
+    struct OutputPartialMock : public OutputPartialMockBase {
+        // Sets up the helper functions called by the function under test to use
+        // mock implementations.
+        MOCK_METHOD1(chooseCompositionStrategy,
+                     bool(std::optional<android::HWComposer::DeviceRequestedChanges>*));
+        MOCK_METHOD0(updateProtectedContentState, void());
+        MOCK_METHOD2(dequeueRenderBuffer,
+                     bool(base::unique_fd*, std::shared_ptr<renderengine::ExternalTexture>*));
+        MOCK_METHOD1(
+                chooseCompositionStrategyAsync,
+                std::future<bool>(std::optional<android::HWComposer::DeviceRequestedChanges>*));
+        MOCK_METHOD4(composeSurfaces,
+                     std::optional<base::unique_fd>(
+                             const Region&, const compositionengine::CompositionRefreshArgs&,
+                             std::shared_ptr<renderengine::ExternalTexture>, base::unique_fd&));
+        MOCK_METHOD0(resetCompositionStrategy, void());
+    };
+
+    OutputPrepareFrameAsyncTest() {
+        mOutput.setDisplayColorProfileForTest(
+                std::unique_ptr<DisplayColorProfile>(mDisplayColorProfile));
+        mOutput.setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
+    }
+
+    StrictMock<mock::CompositionEngine> mCompositionEngine;
+    mock::DisplayColorProfile* mDisplayColorProfile = new StrictMock<mock::DisplayColorProfile>();
+    mock::RenderSurface* mRenderSurface = new StrictMock<mock::RenderSurface>();
+    StrictMock<OutputPartialMock> mOutput;
+    CompositionRefreshArgs mRefreshArgs;
+};
+
+TEST_F(OutputPrepareFrameAsyncTest, delegatesToChooseCompositionStrategyAndRenderSurface) {
+    mOutput.editState().isEnabled = true;
+    mOutput.editState().usesClientComposition = false;
+    mOutput.editState().usesDeviceComposition = true;
+    mOutput.editState().previousDeviceRequestedChanges =
+            std::make_optional<android::HWComposer::DeviceRequestedChanges>({});
+    std::promise<bool> p;
+    p.set_value(true);
+
+    EXPECT_CALL(mOutput, resetCompositionStrategy()).Times(1);
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(0u));
+    EXPECT_CALL(mOutput, updateProtectedContentState());
+    EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _)).WillOnce(Return(true));
+    EXPECT_CALL(*mRenderSurface, prepareFrame(false, true)).Times(1);
+    EXPECT_CALL(mOutput, chooseCompositionStrategyAsync(_))
+            .WillOnce(DoAll(SetArgPointee<0>(mOutput.editState().previousDeviceRequestedChanges),
+                            Return(ByMove(p.get_future()))));
+    EXPECT_CALL(mOutput, composeSurfaces(_, Ref(mRefreshArgs), _, _));
+
+    impl::GpuCompositionResult result = mOutput.prepareFrameAsync(mRefreshArgs);
+    EXPECT_EQ(mOutput.getState().strategyPrediction, CompositionStrategyPredictionState::SUCCESS);
+    EXPECT_FALSE(result.bufferAvailable());
+}
+
+TEST_F(OutputPrepareFrameAsyncTest, skipCompositionOnDequeueFailure) {
+    mOutput.editState().isEnabled = true;
+    mOutput.editState().usesClientComposition = false;
+    mOutput.editState().usesDeviceComposition = true;
+    mOutput.editState().previousDeviceRequestedChanges =
+            std::make_optional<android::HWComposer::DeviceRequestedChanges>({});
+    std::promise<bool> p;
+    p.set_value(true);
+
+    EXPECT_CALL(mOutput, resetCompositionStrategy()).Times(2);
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(0u));
+    EXPECT_CALL(mOutput, updateProtectedContentState());
+    EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _)).WillOnce(Return(false));
+    EXPECT_CALL(*mRenderSurface, prepareFrame(false, true)).Times(2);
+    EXPECT_CALL(mOutput, chooseCompositionStrategyAsync(_))
+            .WillOnce(DoAll(SetArgPointee<0>(mOutput.editState().previousDeviceRequestedChanges),
+                            Return(ByMove(p.get_future()))));
+
+    impl::GpuCompositionResult result = mOutput.prepareFrameAsync(mRefreshArgs);
+    EXPECT_EQ(mOutput.getState().strategyPrediction, CompositionStrategyPredictionState::FAIL);
+    EXPECT_FALSE(result.bufferAvailable());
+}
+
+// Tests that in the event of hwc error when choosing composition strategy, we would fall back
+// client composition
+TEST_F(OutputPrepareFrameAsyncTest, chooseCompositionStrategyFailureCallsPrepareFrame) {
+    mOutput.editState().isEnabled = true;
+    mOutput.editState().usesClientComposition = false;
+    mOutput.editState().usesDeviceComposition = true;
+    mOutput.editState().previousDeviceRequestedChanges =
+            std::make_optional<android::HWComposer::DeviceRequestedChanges>({});
+    std::promise<bool> p;
+    p.set_value(false);
+    std::shared_ptr<renderengine::ExternalTexture> tex =
+            std::make_shared<renderengine::mock::FakeExternalTexture>(1, 1,
+                                                                      HAL_PIXEL_FORMAT_RGBA_8888, 1,
+                                                                      2);
+    EXPECT_CALL(mOutput, resetCompositionStrategy()).Times(2);
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(0u));
+    EXPECT_CALL(mOutput, updateProtectedContentState());
+    EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _))
+            .WillOnce(DoAll(SetArgPointee<1>(tex), Return(true)));
+    EXPECT_CALL(*mRenderSurface, prepareFrame(false, true)).Times(2);
+    EXPECT_CALL(mOutput, chooseCompositionStrategyAsync(_)).WillOnce([&] {
+        return p.get_future();
+    });
+    EXPECT_CALL(mOutput, composeSurfaces(_, Ref(mRefreshArgs), _, _));
+
+    impl::GpuCompositionResult result = mOutput.prepareFrameAsync(mRefreshArgs);
+    EXPECT_EQ(mOutput.getState().strategyPrediction, CompositionStrategyPredictionState::FAIL);
+    EXPECT_TRUE(result.bufferAvailable());
+}
+
+TEST_F(OutputPrepareFrameAsyncTest, predictionMiss) {
+    mOutput.editState().isEnabled = true;
+    mOutput.editState().usesClientComposition = false;
+    mOutput.editState().usesDeviceComposition = true;
+    mOutput.editState().previousDeviceRequestedChanges =
+            std::make_optional<android::HWComposer::DeviceRequestedChanges>({});
+    auto newDeviceRequestedChanges =
+            std::make_optional<android::HWComposer::DeviceRequestedChanges>({});
+    newDeviceRequestedChanges->displayRequests = static_cast<hal::DisplayRequest>(0);
+    std::promise<bool> p;
+    p.set_value(false);
+    std::shared_ptr<renderengine::ExternalTexture> tex =
+            std::make_shared<renderengine::mock::FakeExternalTexture>(1, 1,
+                                                                      HAL_PIXEL_FORMAT_RGBA_8888, 1,
+                                                                      2);
+
+    EXPECT_CALL(mOutput, resetCompositionStrategy()).Times(2);
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(0u));
+    EXPECT_CALL(mOutput, updateProtectedContentState());
+    EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _))
+            .WillOnce(DoAll(SetArgPointee<1>(tex), Return(true)));
+    EXPECT_CALL(*mRenderSurface, prepareFrame(false, true)).Times(2);
+    EXPECT_CALL(mOutput, chooseCompositionStrategyAsync(_)).WillOnce([&] {
+        return p.get_future();
+    });
+    EXPECT_CALL(mOutput, composeSurfaces(_, Ref(mRefreshArgs), _, _));
+
+    impl::GpuCompositionResult result = mOutput.prepareFrameAsync(mRefreshArgs);
+    EXPECT_EQ(mOutput.getState().strategyPrediction, CompositionStrategyPredictionState::FAIL);
+    EXPECT_TRUE(result.bufferAvailable());
 }
 
 /*
@@ -1820,10 +1970,14 @@
         MOCK_METHOD1(setColorTransform, void(const compositionengine::CompositionRefreshArgs&));
         MOCK_METHOD0(beginFrame, void());
         MOCK_METHOD0(prepareFrame, void());
+        MOCK_METHOD1(prepareFrameAsync, GpuCompositionResult(const CompositionRefreshArgs&));
         MOCK_METHOD1(devOptRepaintFlash, void(const compositionengine::CompositionRefreshArgs&));
-        MOCK_METHOD1(finishFrame, void(const compositionengine::CompositionRefreshArgs&));
+        MOCK_METHOD2(finishFrame,
+                     void(const compositionengine::CompositionRefreshArgs&,
+                          GpuCompositionResult&&));
         MOCK_METHOD0(postFramebuffer, void());
         MOCK_METHOD1(renderCachedSets, void(const compositionengine::CompositionRefreshArgs&));
+        MOCK_METHOD1(canPredictCompositionStrategy, bool(const CompositionRefreshArgs&));
     };
 
     StrictMock<OutputPartialMock> mOutput;
@@ -1839,9 +1993,30 @@
     EXPECT_CALL(mOutput, writeCompositionState(Ref(args)));
     EXPECT_CALL(mOutput, setColorTransform(Ref(args)));
     EXPECT_CALL(mOutput, beginFrame());
+    EXPECT_CALL(mOutput, canPredictCompositionStrategy(Ref(args))).WillOnce(Return(false));
     EXPECT_CALL(mOutput, prepareFrame());
     EXPECT_CALL(mOutput, devOptRepaintFlash(Ref(args)));
-    EXPECT_CALL(mOutput, finishFrame(Ref(args)));
+    EXPECT_CALL(mOutput, finishFrame(Ref(args), _));
+    EXPECT_CALL(mOutput, postFramebuffer());
+    EXPECT_CALL(mOutput, renderCachedSets(Ref(args)));
+
+    mOutput.present(args);
+}
+
+TEST_F(OutputPresentTest, predictingCompositionStrategyInvokesPrepareFrameAsync) {
+    CompositionRefreshArgs args;
+
+    InSequence seq;
+    EXPECT_CALL(mOutput, updateColorProfile(Ref(args)));
+    EXPECT_CALL(mOutput, updateCompositionState(Ref(args)));
+    EXPECT_CALL(mOutput, planComposition());
+    EXPECT_CALL(mOutput, writeCompositionState(Ref(args)));
+    EXPECT_CALL(mOutput, setColorTransform(Ref(args)));
+    EXPECT_CALL(mOutput, beginFrame());
+    EXPECT_CALL(mOutput, canPredictCompositionStrategy(Ref(args))).WillOnce(Return(true));
+    EXPECT_CALL(mOutput, prepareFrameAsync(Ref(args)));
+    EXPECT_CALL(mOutput, devOptRepaintFlash(Ref(args)));
+    EXPECT_CALL(mOutput, finishFrame(Ref(args), _));
     EXPECT_CALL(mOutput, postFramebuffer());
     EXPECT_CALL(mOutput, renderCachedSets(Ref(args)));
 
@@ -2739,11 +2914,15 @@
         // Sets up the helper functions called by the function under test to use
         // mock implementations.
         MOCK_METHOD(Region, getDirtyRegion, (), (const));
-        MOCK_METHOD2(composeSurfaces,
+        MOCK_METHOD4(composeSurfaces,
                      std::optional<base::unique_fd>(
-                             const Region&, const compositionengine::CompositionRefreshArgs&));
+                             const Region&, const compositionengine::CompositionRefreshArgs&,
+                             std::shared_ptr<renderengine::ExternalTexture>, base::unique_fd&));
         MOCK_METHOD0(postFramebuffer, void());
         MOCK_METHOD0(prepareFrame, void());
+        MOCK_METHOD0(updateProtectedContentState, void());
+        MOCK_METHOD2(dequeueRenderBuffer,
+                     bool(base::unique_fd*, std::shared_ptr<renderengine::ExternalTexture>*));
     };
 
     OutputDevOptRepaintFlashTest() {
@@ -2800,7 +2979,9 @@
 
     InSequence seq;
     EXPECT_CALL(mOutput, getDirtyRegion()).WillOnce(Return(kNotEmptyRegion));
-    EXPECT_CALL(mOutput, composeSurfaces(RegionEq(kNotEmptyRegion), Ref(mRefreshArgs)));
+    EXPECT_CALL(mOutput, updateProtectedContentState());
+    EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _));
+    EXPECT_CALL(mOutput, composeSurfaces(RegionEq(kNotEmptyRegion), Ref(mRefreshArgs), _, _));
     EXPECT_CALL(*mRenderSurface, queueBuffer(_));
     EXPECT_CALL(mOutput, postFramebuffer());
     EXPECT_CALL(mOutput, prepareFrame());
@@ -2816,10 +2997,14 @@
     struct OutputPartialMock : public OutputPartialMockBase {
         // Sets up the helper functions called by the function under test to use
         // mock implementations.
-        MOCK_METHOD2(composeSurfaces,
+        MOCK_METHOD4(composeSurfaces,
                      std::optional<base::unique_fd>(
-                             const Region&, const compositionengine::CompositionRefreshArgs&));
+                             const Region&, const compositionengine::CompositionRefreshArgs&,
+                             std::shared_ptr<renderengine::ExternalTexture>, base::unique_fd&));
         MOCK_METHOD0(postFramebuffer, void());
+        MOCK_METHOD0(updateProtectedContentState, void());
+        MOCK_METHOD2(dequeueRenderBuffer,
+                     bool(base::unique_fd*, std::shared_ptr<renderengine::ExternalTexture>*));
     };
 
     OutputFinishFrameTest() {
@@ -2837,27 +3022,62 @@
 TEST_F(OutputFinishFrameTest, ifNotEnabledDoesNothing) {
     mOutput.mState.isEnabled = false;
 
-    mOutput.finishFrame(mRefreshArgs);
+    impl::GpuCompositionResult result;
+    mOutput.finishFrame(mRefreshArgs, std::move(result));
 }
 
 TEST_F(OutputFinishFrameTest, takesEarlyOutifComposeSurfacesReturnsNoFence) {
     mOutput.mState.isEnabled = true;
+    EXPECT_CALL(mOutput, updateProtectedContentState());
+    EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _)).WillOnce(Return(true));
+    EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION), _, _, _));
 
-    InSequence seq;
-    EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION), _));
-
-    mOutput.finishFrame(mRefreshArgs);
+    impl::GpuCompositionResult result;
+    mOutput.finishFrame(mRefreshArgs, std::move(result));
 }
 
 TEST_F(OutputFinishFrameTest, queuesBufferIfComposeSurfacesReturnsAFence) {
     mOutput.mState.isEnabled = true;
 
     InSequence seq;
-    EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION), _))
+    EXPECT_CALL(mOutput, updateProtectedContentState());
+    EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _)).WillOnce(Return(true));
+    EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION), _, _, _))
             .WillOnce(Return(ByMove(base::unique_fd())));
     EXPECT_CALL(*mRenderSurface, queueBuffer(_));
 
-    mOutput.finishFrame(mRefreshArgs);
+    impl::GpuCompositionResult result;
+    mOutput.finishFrame(mRefreshArgs, std::move(result));
+}
+
+TEST_F(OutputFinishFrameTest, predictionSucceeded) {
+    mOutput.mState.isEnabled = true;
+    mOutput.mState.strategyPrediction = CompositionStrategyPredictionState::SUCCESS;
+    InSequence seq;
+    EXPECT_CALL(*mRenderSurface, queueBuffer(_));
+
+    impl::GpuCompositionResult result;
+    mOutput.finishFrame(mRefreshArgs, std::move(result));
+}
+
+TEST_F(OutputFinishFrameTest, predictionFailedAndBufferIsReused) {
+    mOutput.mState.isEnabled = true;
+    mOutput.mState.strategyPrediction = CompositionStrategyPredictionState::FAIL;
+
+    InSequence seq;
+
+    impl::GpuCompositionResult result;
+    result.buffer =
+            std::make_shared<renderengine::mock::FakeExternalTexture>(1, 1,
+                                                                      HAL_PIXEL_FORMAT_RGBA_8888, 1,
+                                                                      2);
+
+    EXPECT_CALL(mOutput,
+                composeSurfaces(RegionEq(Region::INVALID_REGION), _, result.buffer,
+                                Eq(ByRef(result.fence))))
+            .WillOnce(Return(ByMove(base::unique_fd())));
+    EXPECT_CALL(*mRenderSurface, queueBuffer(_));
+    mOutput.finishFrame(mRefreshArgs, std::move(result));
 }
 
 /*
@@ -3104,8 +3324,15 @@
 
     struct ExecuteState : public CallOrderStateMachineHelper<TestType, ExecuteState> {
         auto execute() {
-            getInstance()->mReadyFence =
-                    getInstance()->mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs);
+            base::unique_fd fence;
+            std::shared_ptr<renderengine::ExternalTexture> externalTexture;
+            const bool success =
+                    getInstance()->mOutput.dequeueRenderBuffer(&fence, &externalTexture);
+            if (success) {
+                getInstance()->mReadyFence =
+                        getInstance()->mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs,
+                                                               externalTexture, fence);
+            }
             return nextState<FenceCheckState>();
         }
     };
@@ -3475,6 +3702,25 @@
           : public CallOrderStateMachineHelper<TestType, OutputWithDisplayBrightnessNits> {
         auto withDisplayBrightnessNits(float nits) {
             getInstance()->mOutput.mState.displayBrightnessNits = nits;
+            return nextState<OutputWithDimmingStage>();
+        }
+    };
+
+    struct OutputWithDimmingStage
+          : public CallOrderStateMachineHelper<TestType, OutputWithDimmingStage> {
+        auto withDimmingStage(
+                aidl::android::hardware::graphics::composer3::DimmingStage dimmingStage) {
+            getInstance()->mOutput.mState.clientTargetDimmingStage = dimmingStage;
+            return nextState<OutputWithRenderIntent>();
+        }
+    };
+
+    struct OutputWithRenderIntent
+          : public CallOrderStateMachineHelper<TestType, OutputWithRenderIntent> {
+        auto withRenderIntent(
+                aidl::android::hardware::graphics::composer3::RenderIntent renderIntent) {
+            getInstance()->mOutput.mState.renderIntent =
+                    static_cast<ui::RenderIntent>(renderIntent);
             return nextState<SkipColorTransformState>();
         }
     };
@@ -3507,16 +3753,24 @@
     verify().ifMixedCompositionIs(true)
             .andIfUsesHdr(true)
             .withDisplayBrightnessNits(kUnknownLuminance)
+            .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR)
+            .withRenderIntent(
+                    aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC)
             .andIfSkipColorTransform(false)
-            .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip,
-                                            .clip = kDefaultOutputViewport,
-                                            .maxLuminance = kDefaultMaxLuminance,
-                                            .currentLuminanceNits = kDefaultMaxLuminance,
-                                            .outputDataspace = kDefaultOutputDataspace,
-                                            .colorTransform = kDefaultColorTransformMat,
-                                            .deviceHandlesColorTransform = true,
-                                            .orientation = kDefaultOutputOrientationFlags,
-                                            .targetLuminanceNits = kClientTargetLuminanceNits})
+            .thenExpectDisplaySettingsUsed(
+                    {.physicalDisplay = kDefaultOutputDestinationClip,
+                     .clip = kDefaultOutputViewport,
+                     .maxLuminance = kDefaultMaxLuminance,
+                     .currentLuminanceNits = kDefaultMaxLuminance,
+                     .outputDataspace = kDefaultOutputDataspace,
+                     .colorTransform = kDefaultColorTransformMat,
+                     .deviceHandlesColorTransform = true,
+                     .orientation = kDefaultOutputOrientationFlags,
+                     .targetLuminanceNits = kClientTargetLuminanceNits,
+                     .dimmingStage =
+                             aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR,
+                     .renderIntent = aidl::android::hardware::graphics::composer3::RenderIntent::
+                             COLORIMETRIC})
             .execute()
             .expectAFenceWasReturned();
 }
@@ -3526,16 +3780,78 @@
     verify().ifMixedCompositionIs(true)
             .andIfUsesHdr(true)
             .withDisplayBrightnessNits(kDisplayLuminance)
+            .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR)
+            .withRenderIntent(
+                    aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC)
             .andIfSkipColorTransform(false)
-            .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip,
-                                            .clip = kDefaultOutputViewport,
-                                            .maxLuminance = kDefaultMaxLuminance,
-                                            .currentLuminanceNits = kDisplayLuminance,
-                                            .outputDataspace = kDefaultOutputDataspace,
-                                            .colorTransform = kDefaultColorTransformMat,
-                                            .deviceHandlesColorTransform = true,
-                                            .orientation = kDefaultOutputOrientationFlags,
-                                            .targetLuminanceNits = kClientTargetLuminanceNits})
+            .thenExpectDisplaySettingsUsed(
+                    {.physicalDisplay = kDefaultOutputDestinationClip,
+                     .clip = kDefaultOutputViewport,
+                     .maxLuminance = kDefaultMaxLuminance,
+                     .currentLuminanceNits = kDisplayLuminance,
+                     .outputDataspace = kDefaultOutputDataspace,
+                     .colorTransform = kDefaultColorTransformMat,
+                     .deviceHandlesColorTransform = true,
+                     .orientation = kDefaultOutputOrientationFlags,
+                     .targetLuminanceNits = kClientTargetLuminanceNits,
+                     .dimmingStage =
+                             aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR,
+                     .renderIntent = aidl::android::hardware::graphics::composer3::RenderIntent::
+                             COLORIMETRIC})
+            .execute()
+            .expectAFenceWasReturned();
+}
+
+TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings,
+       forHdrMixedCompositionWithDimmingStage) {
+    verify().ifMixedCompositionIs(true)
+            .andIfUsesHdr(true)
+            .withDisplayBrightnessNits(kUnknownLuminance)
+            .withDimmingStage(
+                    aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF)
+            .withRenderIntent(
+                    aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC)
+            .andIfSkipColorTransform(false)
+            .thenExpectDisplaySettingsUsed(
+                    {.physicalDisplay = kDefaultOutputDestinationClip,
+                     .clip = kDefaultOutputViewport,
+                     .maxLuminance = kDefaultMaxLuminance,
+                     .currentLuminanceNits = kDefaultMaxLuminance,
+                     .outputDataspace = kDefaultOutputDataspace,
+                     .colorTransform = kDefaultColorTransformMat,
+                     .deviceHandlesColorTransform = true,
+                     .orientation = kDefaultOutputOrientationFlags,
+                     .targetLuminanceNits = kClientTargetLuminanceNits,
+                     .dimmingStage =
+                             aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF,
+                     .renderIntent = aidl::android::hardware::graphics::composer3::RenderIntent::
+                             COLORIMETRIC})
+            .execute()
+            .expectAFenceWasReturned();
+}
+
+TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings,
+       forHdrMixedCompositionWithRenderIntent) {
+    verify().ifMixedCompositionIs(true)
+            .andIfUsesHdr(true)
+            .withDisplayBrightnessNits(kUnknownLuminance)
+            .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR)
+            .withRenderIntent(aidl::android::hardware::graphics::composer3::RenderIntent::ENHANCE)
+            .andIfSkipColorTransform(false)
+            .thenExpectDisplaySettingsUsed(
+                    {.physicalDisplay = kDefaultOutputDestinationClip,
+                     .clip = kDefaultOutputViewport,
+                     .maxLuminance = kDefaultMaxLuminance,
+                     .currentLuminanceNits = kDefaultMaxLuminance,
+                     .outputDataspace = kDefaultOutputDataspace,
+                     .colorTransform = kDefaultColorTransformMat,
+                     .deviceHandlesColorTransform = true,
+                     .orientation = kDefaultOutputOrientationFlags,
+                     .targetLuminanceNits = kClientTargetLuminanceNits,
+                     .dimmingStage =
+                             aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR,
+                     .renderIntent =
+                             aidl::android::hardware::graphics::composer3::RenderIntent::ENHANCE})
             .execute()
             .expectAFenceWasReturned();
 }
@@ -3544,16 +3860,24 @@
     verify().ifMixedCompositionIs(true)
             .andIfUsesHdr(false)
             .withDisplayBrightnessNits(kUnknownLuminance)
+            .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR)
+            .withRenderIntent(
+                    aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC)
             .andIfSkipColorTransform(false)
-            .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip,
-                                            .clip = kDefaultOutputViewport,
-                                            .maxLuminance = kDefaultMaxLuminance,
-                                            .currentLuminanceNits = kDefaultMaxLuminance,
-                                            .outputDataspace = kDefaultOutputDataspace,
-                                            .colorTransform = kDefaultColorTransformMat,
-                                            .deviceHandlesColorTransform = true,
-                                            .orientation = kDefaultOutputOrientationFlags,
-                                            .targetLuminanceNits = kClientTargetLuminanceNits})
+            .thenExpectDisplaySettingsUsed(
+                    {.physicalDisplay = kDefaultOutputDestinationClip,
+                     .clip = kDefaultOutputViewport,
+                     .maxLuminance = kDefaultMaxLuminance,
+                     .currentLuminanceNits = kDefaultMaxLuminance,
+                     .outputDataspace = kDefaultOutputDataspace,
+                     .colorTransform = kDefaultColorTransformMat,
+                     .deviceHandlesColorTransform = true,
+                     .orientation = kDefaultOutputOrientationFlags,
+                     .targetLuminanceNits = kClientTargetLuminanceNits,
+                     .dimmingStage =
+                             aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR,
+                     .renderIntent = aidl::android::hardware::graphics::composer3::RenderIntent::
+                             COLORIMETRIC})
             .execute()
             .expectAFenceWasReturned();
 }
@@ -3562,16 +3886,24 @@
     verify().ifMixedCompositionIs(false)
             .andIfUsesHdr(true)
             .withDisplayBrightnessNits(kUnknownLuminance)
+            .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR)
+            .withRenderIntent(
+                    aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC)
             .andIfSkipColorTransform(false)
-            .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip,
-                                            .clip = kDefaultOutputViewport,
-                                            .maxLuminance = kDefaultMaxLuminance,
-                                            .currentLuminanceNits = kDefaultMaxLuminance,
-                                            .outputDataspace = kDefaultOutputDataspace,
-                                            .colorTransform = kDefaultColorTransformMat,
-                                            .deviceHandlesColorTransform = false,
-                                            .orientation = kDefaultOutputOrientationFlags,
-                                            .targetLuminanceNits = kClientTargetLuminanceNits})
+            .thenExpectDisplaySettingsUsed(
+                    {.physicalDisplay = kDefaultOutputDestinationClip,
+                     .clip = kDefaultOutputViewport,
+                     .maxLuminance = kDefaultMaxLuminance,
+                     .currentLuminanceNits = kDefaultMaxLuminance,
+                     .outputDataspace = kDefaultOutputDataspace,
+                     .colorTransform = kDefaultColorTransformMat,
+                     .deviceHandlesColorTransform = false,
+                     .orientation = kDefaultOutputOrientationFlags,
+                     .targetLuminanceNits = kClientTargetLuminanceNits,
+                     .dimmingStage =
+                             aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR,
+                     .renderIntent = aidl::android::hardware::graphics::composer3::RenderIntent::
+                             COLORIMETRIC})
             .execute()
             .expectAFenceWasReturned();
 }
@@ -3580,16 +3912,24 @@
     verify().ifMixedCompositionIs(false)
             .andIfUsesHdr(false)
             .withDisplayBrightnessNits(kUnknownLuminance)
+            .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR)
+            .withRenderIntent(
+                    aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC)
             .andIfSkipColorTransform(false)
-            .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip,
-                                            .clip = kDefaultOutputViewport,
-                                            .maxLuminance = kDefaultMaxLuminance,
-                                            .currentLuminanceNits = kDefaultMaxLuminance,
-                                            .outputDataspace = kDefaultOutputDataspace,
-                                            .colorTransform = kDefaultColorTransformMat,
-                                            .deviceHandlesColorTransform = false,
-                                            .orientation = kDefaultOutputOrientationFlags,
-                                            .targetLuminanceNits = kClientTargetLuminanceNits})
+            .thenExpectDisplaySettingsUsed(
+                    {.physicalDisplay = kDefaultOutputDestinationClip,
+                     .clip = kDefaultOutputViewport,
+                     .maxLuminance = kDefaultMaxLuminance,
+                     .currentLuminanceNits = kDefaultMaxLuminance,
+                     .outputDataspace = kDefaultOutputDataspace,
+                     .colorTransform = kDefaultColorTransformMat,
+                     .deviceHandlesColorTransform = false,
+                     .orientation = kDefaultOutputOrientationFlags,
+                     .targetLuminanceNits = kClientTargetLuminanceNits,
+                     .dimmingStage =
+                             aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR,
+                     .renderIntent = aidl::android::hardware::graphics::composer3::RenderIntent::
+                             COLORIMETRIC})
             .execute()
             .expectAFenceWasReturned();
 }
@@ -3599,16 +3939,24 @@
     verify().ifMixedCompositionIs(false)
             .andIfUsesHdr(true)
             .withDisplayBrightnessNits(kUnknownLuminance)
+            .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR)
+            .withRenderIntent(
+                    aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC)
             .andIfSkipColorTransform(true)
-            .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip,
-                                            .clip = kDefaultOutputViewport,
-                                            .maxLuminance = kDefaultMaxLuminance,
-                                            .currentLuminanceNits = kDefaultMaxLuminance,
-                                            .outputDataspace = kDefaultOutputDataspace,
-                                            .colorTransform = kDefaultColorTransformMat,
-                                            .deviceHandlesColorTransform = true,
-                                            .orientation = kDefaultOutputOrientationFlags,
-                                            .targetLuminanceNits = kClientTargetLuminanceNits})
+            .thenExpectDisplaySettingsUsed(
+                    {.physicalDisplay = kDefaultOutputDestinationClip,
+                     .clip = kDefaultOutputViewport,
+                     .maxLuminance = kDefaultMaxLuminance,
+                     .currentLuminanceNits = kDefaultMaxLuminance,
+                     .outputDataspace = kDefaultOutputDataspace,
+                     .colorTransform = kDefaultColorTransformMat,
+                     .deviceHandlesColorTransform = true,
+                     .orientation = kDefaultOutputOrientationFlags,
+                     .targetLuminanceNits = kClientTargetLuminanceNits,
+                     .dimmingStage =
+                             aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR,
+                     .renderIntent = aidl::android::hardware::graphics::composer3::RenderIntent::
+                             COLORIMETRIC})
             .execute()
             .expectAFenceWasReturned();
 }
@@ -3666,7 +4014,11 @@
     EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true));
     EXPECT_CALL(mRenderEngine, useProtectedContext(false));
 
-    mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs);
+    base::unique_fd fd;
+    std::shared_ptr<renderengine::ExternalTexture> tex;
+    mOutput.updateProtectedContentState();
+    mOutput.dequeueRenderBuffer(&fd, &tex);
+    mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd);
 }
 
 TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifRenderEngineDoesNotSupportIt) {
@@ -3674,7 +4026,11 @@
     mLayer2.mLayerFEState.hasProtectedContent = true;
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
 
-    mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs);
+    base::unique_fd fd;
+    std::shared_ptr<renderengine::ExternalTexture> tex;
+    mOutput.updateProtectedContentState();
+    mOutput.dequeueRenderBuffer(&fd, &tex);
+    mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd);
 }
 
 TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNoProtectedContentLayers) {
@@ -3686,7 +4042,11 @@
     EXPECT_CALL(mRenderEngine, useProtectedContext(false));
     EXPECT_CALL(*mRenderSurface, setProtected(false));
 
-    mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs);
+    base::unique_fd fd;
+    std::shared_ptr<renderengine::ExternalTexture> tex;
+    mOutput.updateProtectedContentState();
+    mOutput.dequeueRenderBuffer(&fd, &tex);
+    mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd);
 }
 
 TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNotEnabled) {
@@ -3708,7 +4068,11 @@
             .WillOnce(Return(ByMove(
                     futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
 
-    mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs);
+    base::unique_fd fd;
+    std::shared_ptr<renderengine::ExternalTexture> tex;
+    mOutput.updateProtectedContentState();
+    mOutput.dequeueRenderBuffer(&fd, &tex);
+    mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd);
 }
 
 TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledEverywhere) {
@@ -3718,7 +4082,11 @@
     EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true));
     EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true));
 
-    mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs);
+    base::unique_fd fd;
+    std::shared_ptr<renderengine::ExternalTexture> tex;
+    mOutput.updateProtectedContentState();
+    mOutput.dequeueRenderBuffer(&fd, &tex);
+    mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd);
 }
 
 TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifFailsToEnableInRenderEngine) {
@@ -3729,7 +4097,11 @@
     EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(false));
     EXPECT_CALL(mRenderEngine, useProtectedContext(true));
 
-    mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs);
+    base::unique_fd fd;
+    std::shared_ptr<renderengine::ExternalTexture> tex;
+    mOutput.updateProtectedContentState();
+    mOutput.dequeueRenderBuffer(&fd, &tex);
+    mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd);
 }
 
 TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledInRenderEngine) {
@@ -3740,7 +4112,11 @@
     EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(false));
     EXPECT_CALL(*mRenderSurface, setProtected(true));
 
-    mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs);
+    base::unique_fd fd;
+    std::shared_ptr<renderengine::ExternalTexture> tex;
+    mOutput.updateProtectedContentState();
+    mOutput.dequeueRenderBuffer(&fd, &tex);
+    mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd);
 }
 
 TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledInRenderSurface) {
@@ -3751,7 +4127,11 @@
     EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true));
     EXPECT_CALL(mRenderEngine, useProtectedContext(true));
 
-    mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs);
+    base::unique_fd fd;
+    std::shared_ptr<renderengine::ExternalTexture> tex;
+    mOutput.updateProtectedContentState();
+    mOutput.dequeueRenderBuffer(&fd, &tex);
+    mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd);
 }
 
 struct OutputComposeSurfacesTest_SetsExpensiveRendering : public OutputComposeSurfacesTest {
@@ -3781,7 +4161,11 @@
             .WillOnce(Return(ByMove(
                     futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
 
-    mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs);
+    base::unique_fd fd;
+    std::shared_ptr<renderengine::ExternalTexture> tex;
+    mOutput.updateProtectedContentState();
+    mOutput.dequeueRenderBuffer(&fd, &tex);
+    mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd);
 }
 
 struct OutputComposeSurfacesTest_SetsExpensiveRendering_ForBlur
@@ -3816,7 +4200,12 @@
     mOutput.writeCompositionState(mRefreshArgs);
 
     EXPECT_CALL(mOutput, setExpensiveRenderingExpected(true));
-    mOutput.composeSurfaces(kDebugRegion, mRefreshArgs);
+
+    base::unique_fd fd;
+    std::shared_ptr<renderengine::ExternalTexture> tex;
+    mOutput.updateProtectedContentState();
+    mOutput.dequeueRenderBuffer(&fd, &tex);
+    mOutput.composeSurfaces(kDebugRegion, mRefreshArgs, tex, fd);
 }
 
 TEST_F(OutputComposeSurfacesTest_SetsExpensiveRendering_ForBlur, IfBlursAreNotExpensive) {
@@ -3826,7 +4215,12 @@
     mOutput.writeCompositionState(mRefreshArgs);
 
     EXPECT_CALL(mOutput, setExpensiveRenderingExpected(true)).Times(0);
-    mOutput.composeSurfaces(kDebugRegion, mRefreshArgs);
+
+    base::unique_fd fd;
+    std::shared_ptr<renderengine::ExternalTexture> tex;
+    mOutput.updateProtectedContentState();
+    mOutput.dequeueRenderBuffer(&fd, &tex);
+    mOutput.composeSurfaces(kDebugRegion, mRefreshArgs, tex, fd);
 }
 
 /*
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
index bd4ff13..5c6e8da 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
@@ -97,12 +97,12 @@
     void verifyUniqueDifferingFields(const LayerState& lhs, const LayerState& rhs) {
         EXPECT_EQ(lhs.getHash(), rhs.getHash());
 
-        EXPECT_EQ(Flags<LayerStateField>(LayerStateField::None), lhs.getDifferingFields(rhs));
-        EXPECT_EQ(Flags<LayerStateField>(LayerStateField::None), rhs.getDifferingFields(lhs));
+        EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::None), lhs.getDifferingFields(rhs));
+        EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::None), rhs.getDifferingFields(lhs));
     }
 
     void verifyNonUniqueDifferingFields(const LayerState& lhs, const LayerState& rhs,
-                                        Flags<LayerStateField> fields) {
+                                        ftl::Flags<LayerStateField> fields) {
         EXPECT_NE(lhs.getHash(), rhs.getHash());
 
         EXPECT_EQ(fields, lhs.getDifferingFields(rhs));
@@ -159,9 +159,9 @@
     sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionState, sSequenceIdTwo);
-    Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+    ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
     EXPECT_EQ(sSequenceIdTwo, mLayerState->getId());
-    EXPECT_EQ(Flags<LayerStateField>(LayerStateField::Id), updates);
+    EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::Id), updates);
 }
 
 TEST_F(LayerStateTest, compareId) {
@@ -204,9 +204,9 @@
     sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionState, sSequenceId, sDebugNameTwo);
-    Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+    ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
     EXPECT_EQ(sDebugNameTwo, mLayerState->getName());
-    EXPECT_EQ(Flags<LayerStateField>(LayerStateField::Name), updates);
+    EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::Name), updates);
 }
 
 TEST_F(LayerStateTest, compareName) {
@@ -253,9 +253,9 @@
     outputLayerCompositionStateTwo.displayFrame = sRectTwo;
     setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionStateTwo,
                        layerFECompositionState);
-    Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+    ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
     EXPECT_EQ(sRectTwo, mLayerState->getDisplayFrame());
-    EXPECT_EQ(Flags<LayerStateField>(LayerStateField::DisplayFrame), updates);
+    EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::DisplayFrame), updates);
 }
 
 TEST_F(LayerStateTest, compareDisplayFrame) {
@@ -315,9 +315,9 @@
     layerFECompositionStateTwo.compositionType = Composition::SOLID_COLOR;
     setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
-    Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+    ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
     EXPECT_EQ(Composition::SOLID_COLOR, mLayerState->getCompositionType());
-    EXPECT_EQ(Flags<LayerStateField>(LayerStateField::CompositionType), updates);
+    EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::CompositionType), updates);
 }
 
 TEST_F(LayerStateTest, compareCompositionType) {
@@ -357,8 +357,8 @@
     layerFECompositionStateTwo.buffer = new GraphicBuffer();
     setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
-    Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
-    EXPECT_EQ(Flags<LayerStateField>(LayerStateField::Buffer), updates);
+    ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+    EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::Buffer), updates);
 }
 
 TEST_F(LayerStateTest, updateBufferSingleBufferedLegacy) {
@@ -380,8 +380,8 @@
         layerFECompositionStateTwo.frameNumber = i;
         setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                            layerFECompositionStateTwo);
-        Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
-        EXPECT_EQ(Flags<LayerStateField>(LayerStateField::Buffer), updates);
+        ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+        EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::Buffer), updates);
     }
 }
 
@@ -404,8 +404,8 @@
     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);
+        ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+        EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::Buffer), updates);
     }
 }
 
@@ -446,8 +446,8 @@
     outputLayerCompositionStateTwo.sourceCrop = sFloatRectTwo;
     setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionStateTwo,
                        layerFECompositionState);
-    Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
-    EXPECT_EQ(Flags<LayerStateField>(LayerStateField::SourceCrop), updates);
+    ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+    EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::SourceCrop), updates);
 }
 
 TEST_F(LayerStateTest, compareSourceCrop) {
@@ -485,8 +485,8 @@
     outputLayerCompositionStateTwo.bufferTransform = Hwc2::Transform::FLIP_V;
     setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionStateTwo,
                        layerFECompositionState);
-    Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
-    EXPECT_EQ(Flags<LayerStateField>(LayerStateField::BufferTransform), updates);
+    ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+    EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::BufferTransform), updates);
 }
 
 TEST_F(LayerStateTest, compareBufferTransform) {
@@ -525,8 +525,8 @@
     layerFECompositionStateTwo.blendMode = hal::BlendMode::PREMULTIPLIED;
     setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
-    Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
-    EXPECT_EQ(Flags<LayerStateField>(LayerStateField::BlendMode), updates);
+    ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+    EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::BlendMode), updates);
 }
 
 TEST_F(LayerStateTest, compareBlendMode) {
@@ -564,8 +564,8 @@
     layerFECompositionStateTwo.alpha = sAlphaTwo;
     setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
-    Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
-    EXPECT_EQ(Flags<LayerStateField>(LayerStateField::Alpha), updates);
+    ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+    EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::Alpha), updates);
 }
 
 TEST_F(LayerStateTest, compareAlpha) {
@@ -603,8 +603,8 @@
     layerFECompositionStateTwo.metadata[sMetadataKeyTwo] = sMetadataValueTwo;
     setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
-    Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
-    EXPECT_EQ(Flags<LayerStateField>(LayerStateField::LayerMetadata), updates);
+    ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+    EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::LayerMetadata), updates);
 }
 
 TEST_F(LayerStateTest, compareLayerMetadata) {
@@ -652,8 +652,8 @@
     outputLayerCompositionStateTwo.visibleRegion = sRegionTwo;
     setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionStateTwo,
                        layerFECompositionState);
-    Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
-    EXPECT_EQ(Flags<LayerStateField>(LayerStateField::VisibleRegion), updates);
+    ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+    EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::VisibleRegion), updates);
 }
 
 TEST_F(LayerStateTest, compareVisibleRegion) {
@@ -691,8 +691,8 @@
     outputLayerCompositionStateTwo.dataspace = ui::Dataspace::DISPLAY_P3;
     setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionStateTwo,
                        layerFECompositionState);
-    Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
-    EXPECT_EQ(Flags<LayerStateField>(LayerStateField::Dataspace), updates);
+    ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+    EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::Dataspace), updates);
 }
 
 TEST_F(LayerStateTest, compareDataspace) {
@@ -738,9 +738,9 @@
                               "buffer2");
     setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
-    Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
-    EXPECT_EQ(Flags<LayerStateField>(LayerStateField::Buffer) |
-                      Flags<LayerStateField>(LayerStateField::PixelFormat),
+    ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+    EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::Buffer) |
+                      ftl::Flags<LayerStateField>(LayerStateField::PixelFormat),
               updates);
 }
 
@@ -768,7 +768,7 @@
     auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
 
     verifyNonUniqueDifferingFields(*mLayerState, *otherLayerState,
-                                   Flags<LayerStateField>(LayerStateField::PixelFormat));
+                                   ftl::Flags<LayerStateField>(LayerStateField::PixelFormat));
 
     EXPECT_TRUE(mLayerState->compare(*otherLayerState));
     EXPECT_TRUE(otherLayerState->compare(*mLayerState));
@@ -790,8 +790,8 @@
     layerFECompositionStateTwo.colorTransform = sMat4One;
     setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
-    Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
-    EXPECT_EQ(Flags<LayerStateField>(LayerStateField::ColorTransform), updates);
+    ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+    EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::ColorTransform), updates);
 }
 
 TEST_F(LayerStateTest, compareColorTransform) {
@@ -831,8 +831,8 @@
     layerFECompositionStateTwo.sidebandStream = NativeHandle::create(sFakeSidebandStreamTwo, false);
     setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
-    Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
-    EXPECT_EQ(Flags<LayerStateField>(LayerStateField::SidebandStream), updates);
+    ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+    EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::SidebandStream), updates);
 }
 
 TEST_F(LayerStateTest, compareSidebandStream) {
@@ -870,8 +870,8 @@
     layerFECompositionStateTwo.color = sHalf4Two;
     setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
-    Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
-    EXPECT_EQ(Flags<LayerStateField>(LayerStateField::SolidColor), updates);
+    ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+    EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::SolidColor), updates);
 }
 
 TEST_F(LayerStateTest, compareSolidColor) {
@@ -909,8 +909,8 @@
     layerFECompositionStateTwo.backgroundBlurRadius = sBgBlurRadiusTwo;
     setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
-    Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
-    EXPECT_EQ(Flags<LayerStateField>(LayerStateField::BackgroundBlurRadius), updates);
+    ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+    EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::BackgroundBlurRadius), updates);
 }
 
 TEST_F(LayerStateTest, compareBackgroundBlur) {
@@ -949,8 +949,8 @@
     layerFECompositionStateTwo.blurRegions.push_back(sBlurRegionTwo);
     setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
-    Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
-    EXPECT_EQ(Flags<LayerStateField>(LayerStateField::BlurRegions), updates);
+    ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+    EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::BlurRegions), updates);
 }
 
 TEST_F(LayerStateTest, compareBlurRegions) {
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index f5a4b3d..52529d6 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -24,7 +24,6 @@
 
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
-#include <android-base/stringprintf.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/Display.h>
 #include <compositionengine/DisplayColorProfile.h>
@@ -49,8 +48,6 @@
 
 namespace hal = hardware::graphics::composer::hal;
 
-using android::base::StringAppendF;
-
 ui::Transform::RotationFlags DisplayDevice::sPrimaryDisplayRotationFlags = ui::Transform::ROT_0;
 
 DisplayDeviceCreationArgs::DisplayDeviceCreationArgs(
@@ -91,6 +88,7 @@
                 static_cast<uint32_t>(SurfaceFlinger::maxFrameBufferAcquiredBuffers));
     }
 
+    mCompositionDisplay->setPredictCompositionStrategy(mFlinger->mPredictCompositionStrategy);
     mCompositionDisplay->createDisplayColorProfile(
             compositionengine::DisplayColorProfileCreationArgsBuilder()
                     .setHasWideColorGamut(args.hasWideColorGamut)
@@ -298,26 +296,24 @@
         sPrimaryDisplayRotationFlags = ui::Transform::toRotationFlags(orientation);
     }
 
-    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.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.getBoundsAsRect();
-        if (orientation == ui::ROTATION_90 || orientation == ui::ROTATION_270) {
-            std::swap(layerStackSpaceRect.right, layerStackSpaceRect.bottom);
-        }
-    }
-
     // We need to take care of display rotation for globalTransform for case if the panel is not
     // installed aligned with device orientation.
     const auto transformOrientation = orientation + mPhysicalOrientation;
+
+    const auto& state = getCompositionDisplay()->getState();
+
+    // If the layer stack and destination frames have never been set, then configure them to be the
+    // same as the physical device, taking into account the total transform.
+    if (!orientedDisplaySpaceRect.isValid()) {
+        ui::Size bounds = state.displaySpace.getBounds();
+        bounds.rotate(transformOrientation);
+        orientedDisplaySpaceRect = Rect(bounds);
+    }
+    if (layerStackSpaceRect.isEmpty()) {
+        ui::Size bounds = state.framebufferSpace.getBounds();
+        bounds.rotate(transformOrientation);
+        layerStackSpaceRect = Rect(bounds);
+    }
     getCompositionDisplay()->setProjection(transformOrientation, layerStackSpaceRect,
                                            orientedDisplaySpaceRect);
 }
@@ -343,38 +339,40 @@
 }
 
 std::string DisplayDevice::getDebugName() const {
-    const char* type = "virtual";
+    using namespace std::string_literals;
+
+    std::string name = "Display "s + to_string(getId()) + " ("s;
+
     if (mConnectionType) {
-        type = isInternal() ? "internal" : "external";
+        name += isInternal() ? "internal"s : "external"s;
+    } else {
+        name += "virtual"s;
     }
 
-    return base::StringPrintf("DisplayDevice{%s, %s%s, \"%s\"}", to_string(getId()).c_str(), type,
-                              isPrimary() ? ", primary" : "", mDisplayName.c_str());
+    if (isPrimary()) {
+        name += ", primary"s;
+    }
+
+    return name + ", \""s + mDisplayName + "\")"s;
 }
 
 void DisplayDevice::dump(std::string& result) const {
-    StringAppendF(&result, "+ %s\n", getDebugName().c_str());
-    StringAppendF(&result, "   powerMode=%s (%d)\n", to_string(mPowerMode).c_str(),
-                  static_cast<int32_t>(mPowerMode));
-    const auto activeMode = getActiveMode();
-    StringAppendF(&result, "   activeMode=%s\n",
-                  activeMode ? to_string(*activeMode).c_str() : "none");
+    using namespace std::string_literals;
 
-    result.append("   supportedModes=\n");
-    for (const auto& [id, mode] : mSupportedModes) {
-        result.append("      ");
-        result.append(to_string(*mode));
-        result.push_back('\n');
+    result += getDebugName();
+
+    if (!isVirtual()) {
+        result += "\n   deviceProductInfo="s;
+        if (mDeviceProductInfo) {
+            mDeviceProductInfo->dump(result);
+        } else {
+            result += "{}"s;
+        }
     }
 
-    StringAppendF(&result, "   deviceProductInfo=");
-    if (mDeviceProductInfo) {
-        mDeviceProductInfo->dump(result);
-    } else {
-        result.append("{}");
-    }
-    result.append("\n");
-    getCompositionDisplay()->dump(result);
+    result += "\n   powerMode="s;
+    result += to_string(mPowerMode);
+    result += '\n';
 
     if (mRefreshRateConfigs) {
         mRefreshRateConfigs->dump(result);
@@ -471,9 +469,8 @@
         return;
     }
 
-    const auto [lowFps, highFps] = mRefreshRateConfigs->getSupportedRefreshRateRange();
-    mRefreshRateOverlay = std::make_unique<RefreshRateOverlay>(*mFlinger, lowFps.getIntValue(),
-                                                               highFps.getIntValue(), showSpinnner);
+    const auto fpsRange = mRefreshRateConfigs->getSupportedRefreshRateRange();
+    mRefreshRateOverlay = std::make_unique<RefreshRateOverlay>(fpsRange, showSpinnner);
     mRefreshRateOverlay->setLayerStack(getLayerStack());
     mRefreshRateOverlay->setViewport(getSize());
     mRefreshRateOverlay->changeRefreshRate(getActiveMode()->getFps());
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 2c4a300..d5d87b4 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -21,6 +21,7 @@
 #include <string>
 #include <unordered_map>
 
+#include <android-base/thread_annotations.h>
 #include <android/native_window.h>
 #include <binder/IBinder.h>
 #include <gui/LayerState.h>
@@ -28,6 +29,7 @@
 #include <renderengine/RenderEngine.h>
 #include <system/window.h>
 #include <ui/DisplayId.h>
+#include <ui/DisplayIdentification.h>
 #include <ui/DisplayState.h>
 #include <ui/GraphicTypes.h>
 #include <ui/HdrCapabilities.h>
@@ -39,15 +41,11 @@
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
 
-#include "MainThreadGuard.h"
-
-#include <ui/DisplayIdentification.h>
 #include "DisplayHardware/DisplayMode.h"
 #include "DisplayHardware/Hal.h"
 #include "DisplayHardware/PowerAdvisor.h"
-
 #include "Scheduler/RefreshRateConfigs.h"
-
+#include "ThreadContext.h"
 #include "TracedOrdinal.h"
 
 namespace android {
@@ -99,9 +97,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 stageBrightness(float brightness) REQUIRES(kMainThreadContext);
+    void persistBrightness(bool needsComposite) REQUIRES(kMainThreadContext);
+    bool isBrightnessStale() const REQUIRES(kMainThreadContext);
     void setFlags(uint32_t flags);
 
     ui::Rotation getPhysicalOrientation() const { return mPhysicalOrientation; }
@@ -109,7 +107,7 @@
 
     static ui::Transform::RotationFlags getPrimaryDisplayRotationFlags();
 
-    std::optional<float> getStagedBrightness() const REQUIRES(SF_MAIN_THREAD);
+    std::optional<float> getStagedBrightness() const REQUIRES(kMainThreadContext);
     ui::Transform::RotationFlags getTransformHint() const;
     const ui::Transform& getTransform() const;
     const Rect& getLayerStackSpaceRect() const;
@@ -209,15 +207,15 @@
     bool setDesiredActiveMode(const ActiveModeInfo&) EXCLUDES(mActiveModeLock);
     std::optional<ActiveModeInfo> getDesiredActiveMode() const EXCLUDES(mActiveModeLock);
     void clearDesiredActiveModeState() EXCLUDES(mActiveModeLock);
-    ActiveModeInfo getUpcomingActiveMode() const REQUIRES(SF_MAIN_THREAD) {
+    ActiveModeInfo getUpcomingActiveMode() const REQUIRES(kMainThreadContext) {
         return mUpcomingActiveMode;
     }
 
-    void setActiveMode(DisplayModeId) REQUIRES(SF_MAIN_THREAD);
+    void setActiveMode(DisplayModeId) REQUIRES(kMainThreadContext);
     status_t initiateModeChange(const ActiveModeInfo&,
                                 const hal::VsyncPeriodChangeConstraints& constraints,
                                 hal::VsyncPeriodChangeTimeline* outTimeline)
-            REQUIRES(SF_MAIN_THREAD);
+            REQUIRES(kMainThreadContext);
 
     // Return the immutable list of supported display modes. The HWC may report different modes
     // after a hotplug reconnect event, in which case the DisplayDevice object will be recreated.
@@ -304,7 +302,7 @@
     ActiveModeInfo mDesiredActiveMode GUARDED_BY(mActiveModeLock);
     TracedOrdinal<bool> mDesiredActiveModeChanged
             GUARDED_BY(mActiveModeLock) = {"DesiredActiveModeChanged", false};
-    ActiveModeInfo mUpcomingActiveMode GUARDED_BY(SF_MAIN_THREAD);
+    ActiveModeInfo mUpcomingActiveMode GUARDED_BY(kMainThreadContext);
 };
 
 struct DisplayDeviceState {
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 297a776..79dcd15 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -41,6 +41,7 @@
 
 using aidl::android::hardware::graphics::composer3::BnComposerCallback;
 using aidl::android::hardware::graphics::composer3::Capability;
+using aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness;
 using aidl::android::hardware::graphics::composer3::PowerMode;
 using aidl::android::hardware::graphics::composer3::VirtualDisplay;
 
@@ -155,15 +156,6 @@
             .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()));
 }
@@ -935,9 +927,9 @@
     return Error::NONE;
 }
 
-Error AidlComposer::setDisplayBrightness(Display display, float brightness,
+Error AidlComposer::setDisplayBrightness(Display display, float brightness, float brightnessNits,
                                          const DisplayBrightnessOptions& options) {
-    mWriter.setDisplayBrightness(translate<int64_t>(display), brightness);
+    mWriter.setDisplayBrightness(translate<int64_t>(display), brightness, brightnessNits);
 
     if (options.applyImmediately) {
         return execute();
@@ -1082,12 +1074,8 @@
 }
 
 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;
+        Display display, ClientTargetPropertyWithBrightness* outClientTargetProperty) {
+    *outClientTargetProperty = mReader.takeClientTargetProperty(translate<int64_t>(display));
     return Error::NONE;
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
index 28ff167..18d2242 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -183,7 +183,7 @@
     Error setLayerPerFrameMetadataBlobs(
             Display display, Layer layer,
             const std::vector<IComposerClient::PerFrameMetadataBlob>& metadata) override;
-    Error setDisplayBrightness(Display display, float brightness,
+    Error setDisplayBrightness(Display display, float brightness, float brightnessNits,
                                const DisplayBrightnessOptions& options) override;
 
     // Composer HAL 2.4
@@ -208,9 +208,10 @@
                                         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;
+    Error getClientTargetProperty(
+            Display display,
+            aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness*
+                    outClientTargetProperty) override;
 
     // AIDL Composer HAL
     Error setLayerBrightness(Display display, Layer layer, float brightness) override;
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index 2dc0830..d266d94 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -33,6 +33,7 @@
 
 #include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
 #include <aidl/android/hardware/graphics/composer3/Capability.h>
+#include <aidl/android/hardware/graphics/composer3/ClientTargetPropertyWithBrightness.h>
 #include <aidl/android/hardware/graphics/composer3/Color.h>
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
@@ -236,7 +237,7 @@
             return applyImmediately == other.applyImmediately;
         }
     };
-    virtual Error setDisplayBrightness(Display display, float brightness,
+    virtual Error setDisplayBrightness(Display display, float brightness, float brightnessNits,
                                        const DisplayBrightnessOptions& options) = 0;
 
     // Composer HAL 2.4
@@ -264,8 +265,7 @@
             std::vector<IComposerClient::LayerGenericMetadataKey>* outKeys) = 0;
 
     virtual Error getClientTargetProperty(
-            Display display, IComposerClient::ClientTargetProperty* outClientTargetProperty,
-            float* outBrightness) = 0;
+            Display display, V3_0::ClientTargetPropertyWithBrightness* outClientTargetProperty) = 0;
 
     // AIDL Composer
     virtual Error setLayerBrightness(Display display, Layer layer, float brightness) = 0;
diff --git a/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp b/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp
index 8d685cf..eb14933 100644
--- a/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp
+++ b/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp
@@ -44,18 +44,10 @@
 #include "HWComposer.h"
 #include "../SurfaceFlinger.h"
 
-// ----------------------------------------------------------------------------
 namespace android {
-// ----------------------------------------------------------------------------
 
 using ui::Dataspace;
 
-/*
- * This implements the (main) framebuffer management. This class is used
- * mostly by SurfaceFlinger, but also by command line GL application.
- *
- */
-
 FramebufferSurface::FramebufferSurface(HWComposer& hwc, PhysicalDisplayId displayId,
                                        const sp<IGraphicBufferConsumer>& consumer,
                                        const ui::Size& size, const ui::Size& maxSize)
@@ -205,14 +197,14 @@
 
 void FramebufferSurface::dumpAsString(String8& result) const {
     Mutex::Autolock lock(mMutex);
-    result.appendFormat("  FramebufferSurface: dataspace: %s(%d)\n",
+    result.append("   FramebufferSurface\n");
+    result.appendFormat("      mDataSpace=%s (%d)\n",
                         dataspaceDetails(static_cast<android_dataspace>(mDataSpace)).c_str(),
                         mDataSpace);
-    ConsumerBase::dumpLocked(result, "   ");
+    ConsumerBase::dumpLocked(result, "      ");
 }
 
-void FramebufferSurface::dumpLocked(String8& result, const char* prefix) const
-{
+void FramebufferSurface::dumpLocked(String8& result, const char* prefix) const {
     ConsumerBase::dumpLocked(result, prefix);
 }
 
@@ -220,9 +212,7 @@
     return mCurrentFence;
 }
 
-// ----------------------------------------------------------------------------
-}; // namespace android
-// ----------------------------------------------------------------------------
+} // 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/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index c0432bf..adf4be3 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -544,9 +544,11 @@
 }
 
 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);
+        float brightness, float brightnessNits,
+        const Hwc2::Composer::DisplayBrightnessOptions& options) {
+    return ftl::defer([composer = &mComposer, id = mId, brightness, brightnessNits, options] {
+        const auto intError =
+                composer->setDisplayBrightness(id, brightness, brightnessNits, options);
         return static_cast<Error>(intError);
     });
 }
@@ -585,10 +587,10 @@
     return static_cast<Error>(intError);
 }
 
-Error Display::getClientTargetProperty(ClientTargetProperty* outClientTargetProperty,
-                                       float* outWhitePointNits) {
-    const auto error =
-            mComposer.getClientTargetProperty(mId, outClientTargetProperty, outWhitePointNits);
+Error Display::getClientTargetProperty(
+        aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness*
+                outClientTargetProperty) {
+    const auto error = mComposer.getClientTargetProperty(mId, outClientTargetProperty);
     return static_cast<Error>(error);
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index a805566..cca20bd 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -39,6 +39,7 @@
 
 #include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
 #include <aidl/android/hardware/graphics/composer3/Capability.h>
+#include <aidl/android/hardware/graphics/composer3/ClientTargetPropertyWithBrightness.h>
 #include <aidl/android/hardware/graphics/composer3/Color.h>
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
@@ -147,7 +148,8 @@
                                                        android::sp<android::Fence>* outPresentFence,
                                                        uint32_t* state) = 0;
     [[nodiscard]] virtual std::future<hal::Error> setDisplayBrightness(
-            float brightness, const Hwc2::Composer::DisplayBrightnessOptions& options) = 0;
+            float brightness, float brightnessNits,
+            const Hwc2::Composer::DisplayBrightnessOptions& options) = 0;
     [[nodiscard]] virtual hal::Error setActiveConfigWithConstraints(
             hal::HWConfigId configId, const hal::VsyncPeriodChangeConstraints& constraints,
             hal::VsyncPeriodChangeTimeline* outTimeline) = 0;
@@ -160,7 +162,8 @@
             std::vector<hal::ContentType>*) const = 0;
     [[nodiscard]] virtual hal::Error setContentType(hal::ContentType) = 0;
     [[nodiscard]] virtual hal::Error getClientTargetProperty(
-            hal::ClientTargetProperty* outClientTargetProperty, float* outWhitePointNits) = 0;
+            aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness*
+                    outClientTargetProperty) = 0;
     [[nodiscard]] virtual hal::Error getDisplayDecorationSupport(
             std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
                     support) = 0;
@@ -227,7 +230,8 @@
                                  android::sp<android::Fence>* outPresentFence,
                                  uint32_t* state) override;
     std::future<hal::Error> setDisplayBrightness(
-            float brightness, const Hwc2::Composer::DisplayBrightnessOptions& options) override;
+            float brightness, float brightnessNits,
+            const Hwc2::Composer::DisplayBrightnessOptions& options) override;
     hal::Error setActiveConfigWithConstraints(hal::HWConfigId configId,
                                               const hal::VsyncPeriodChangeConstraints& constraints,
                                               hal::VsyncPeriodChangeTimeline* outTimeline) override;
@@ -238,8 +242,9 @@
     hal::Error getSupportedContentTypes(
             std::vector<hal::ContentType>* outSupportedContentTypes) const override;
     hal::Error setContentType(hal::ContentType) override;
-    hal::Error getClientTargetProperty(hal::ClientTargetProperty* outClientTargetProperty,
-                                       float* outWhitePointNits) override;
+    hal::Error getClientTargetProperty(
+            aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness*
+                    outClientTargetProperty) override;
     hal::Error getDisplayDecorationSupport(
             std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
                     support) override;
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 459291a..79e4c75 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -479,12 +479,11 @@
     RETURN_IF_HWC_ERROR_FOR("getRequests", error, displayId, BAD_INDEX);
 
     DeviceRequestedChanges::ClientTargetProperty clientTargetProperty;
-    float brightness = 1.f;
-    error = hwcDisplay->getClientTargetProperty(&clientTargetProperty, &brightness);
+    error = hwcDisplay->getClientTargetProperty(&clientTargetProperty);
 
     outChanges->emplace(DeviceRequestedChanges{std::move(changedTypes), std::move(displayRequests),
                                                std::move(layerRequests),
-                                               std::move(clientTargetProperty), brightness});
+                                               std::move(clientTargetProperty)});
     error = hwcDisplay->acceptChanges();
     RETURN_IF_HWC_ERROR_FOR("acceptChanges", error, displayId, BAD_INDEX);
 
@@ -722,12 +721,12 @@
 }
 
 std::future<status_t> HWComposer::setDisplayBrightness(
-        PhysicalDisplayId displayId, float brightness,
+        PhysicalDisplayId displayId, float brightness, float brightnessNits,
         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, options))
+    return ftl::chain(display->setDisplayBrightness(brightness, brightnessNits, options))
             .then([displayId](hal::Error error) -> status_t {
                 if (error == hal::Error::UNSUPPORTED) {
                     RETURN_IF_HWC_ERROR(error, displayId, INVALID_OPERATION);
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 0e15a7c..7dc10ea 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -45,6 +45,7 @@
 
 #include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
 #include <aidl/android/hardware/graphics/composer3/Capability.h>
+#include <aidl/android/hardware/graphics/composer3/ClientTargetPropertyWithBrightness.h>
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
 
@@ -78,7 +79,8 @@
         using ChangedTypes =
                 std::unordered_map<HWC2::Layer*,
                                    aidl::android::hardware::graphics::composer3::Composition>;
-        using ClientTargetProperty = hal::ClientTargetProperty;
+        using ClientTargetProperty =
+                aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness;
         using DisplayRequests = hal::DisplayRequest;
         using LayerRequests = std::unordered_map<HWC2::Layer*, hal::LayerRequest>;
 
@@ -86,7 +88,6 @@
         DisplayRequests displayRequests;
         LayerRequests layerRequests;
         ClientTargetProperty clientTargetProperty;
-        float clientTargetBrightness;
     };
 
     struct HWCDisplayMode {
@@ -195,7 +196,7 @@
 
     // Sets the brightness of a display.
     virtual std::future<status_t> setDisplayBrightness(
-            PhysicalDisplayId, float brightness,
+            PhysicalDisplayId, float brightness, float brightnessNits,
             const Hwc2::Composer::DisplayBrightnessOptions&) = 0;
 
     // Events handling ---------------------------------------------------------
@@ -279,6 +280,13 @@
     virtual Hwc2::AidlTransform getPhysicalDisplayOrientation(PhysicalDisplayId) const = 0;
 };
 
+static inline bool operator==(const android::HWComposer::DeviceRequestedChanges& lhs,
+                              const android::HWComposer::DeviceRequestedChanges& rhs) {
+    return lhs.changedTypes == rhs.changedTypes && lhs.displayRequests == rhs.displayRequests &&
+            lhs.layerRequests == rhs.layerRequests &&
+            lhs.clientTargetProperty == rhs.clientTargetProperty;
+}
+
 namespace impl {
 
 class HWComposer final : public android::HWComposer {
@@ -365,7 +373,7 @@
     status_t getDisplayedContentSample(HalDisplayId, uint64_t maxFrames, uint64_t timestamp,
                                        DisplayedFrameStats* outStats) override;
     std::future<status_t> setDisplayBrightness(
-            PhysicalDisplayId, float brightness,
+            PhysicalDisplayId, float brightness, float brightnessNits,
             const Hwc2::Composer::DisplayBrightnessOptions&) override;
 
     // Events handling ---------------------------------------------------------
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index d9af553..2597ae6 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -37,6 +37,8 @@
 #include <cinttypes>
 
 using aidl::android::hardware::graphics::composer3::Capability;
+using aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness;
+using aidl::android::hardware::graphics::composer3::DimmingStage;
 using aidl::android::hardware::graphics::composer3::DisplayCapability;
 
 namespace android {
@@ -1113,7 +1115,7 @@
     return Error::NONE;
 }
 
-Error HidlComposer::setDisplayBrightness(Display display, float brightness,
+Error HidlComposer::setDisplayBrightness(Display display, float brightness, float,
                                          const DisplayBrightnessOptions&) {
     if (!mClient_2_3) {
         return Error::UNSUPPORTED;
@@ -1302,10 +1304,17 @@
 }
 
 Error HidlComposer::getClientTargetProperty(
-        Display display, IComposerClient::ClientTargetProperty* outClientTargetProperty,
-        float* outBrightness) {
-    mReader.takeClientTargetProperty(display, outClientTargetProperty);
-    *outBrightness = 1.f;
+        Display display, ClientTargetPropertyWithBrightness* outClientTargetProperty) {
+    IComposerClient::ClientTargetProperty property;
+    mReader.takeClientTargetProperty(display, &property);
+    outClientTargetProperty->display = display;
+    outClientTargetProperty->clientTargetProperty.dataspace =
+            static_cast<::aidl::android::hardware::graphics::common::Dataspace>(property.dataspace);
+    outClientTargetProperty->clientTargetProperty.pixelFormat =
+            static_cast<::aidl::android::hardware::graphics::common::PixelFormat>(
+                    property.pixelFormat);
+    outClientTargetProperty->brightness = 1.f;
+    outClientTargetProperty->dimmingStage = DimmingStage::NONE;
     return Error::NONE;
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
index 5869ae5..d0d3c2e 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -292,7 +292,7 @@
     Error setLayerPerFrameMetadataBlobs(
             Display display, Layer layer,
             const std::vector<IComposerClient::PerFrameMetadataBlob>& metadata) override;
-    Error setDisplayBrightness(Display display, float brightness,
+    Error setDisplayBrightness(Display display, float brightness, float brightnessNits,
                                const DisplayBrightnessOptions& options) override;
 
     // Composer HAL 2.4
@@ -317,9 +317,10 @@
                                         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;
+    Error getClientTargetProperty(
+            Display display,
+            aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness*
+                    outClientTargetProperty) override;
 
     // AIDL Composer HAL
     Error setLayerBrightness(Display display, Layer layer, float brightness) override;
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
index 44c086d..659efd8 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -32,7 +32,6 @@
 #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>
 
@@ -62,8 +61,6 @@
 
 using scheduler::OneShotTimer;
 
-class AidlPowerHalWrapper;
-
 PowerAdvisor::~PowerAdvisor() = default;
 
 namespace {
@@ -294,259 +291,244 @@
     const sp<V1_3::IPower> mPowerHal = nullptr;
 };
 
-class AidlPowerHalWrapper : public PowerAdvisor::HalWrapper {
-public:
-    AidlPowerHalWrapper(sp<IPower> powerHal) : mPowerHal(std::move(powerHal)) {
-        auto ret = mPowerHal->isModeSupported(Mode::EXPENSIVE_RENDERING, &mHasExpensiveRendering);
-        if (!ret.isOk()) {
-            mHasExpensiveRendering = false;
-        }
-
-        ret = mPowerHal->isBoostSupported(Boost::DISPLAY_UPDATE_IMMINENT,
-                                          &mHasDisplayUpdateImminent);
-        if (!ret.isOk()) {
-            mHasDisplayUpdateImminent = false;
-        }
-
-        mSupportsPowerHint = checkPowerHintSessionSupported();
+AidlPowerHalWrapper::AidlPowerHalWrapper(sp<IPower> powerHal) : mPowerHal(std::move(powerHal)) {
+    auto ret = mPowerHal->isModeSupported(Mode::EXPENSIVE_RENDERING, &mHasExpensiveRendering);
+    if (!ret.isOk()) {
+        mHasExpensiveRendering = false;
     }
 
-    ~AidlPowerHalWrapper() override {
-        if (mPowerHintSession != nullptr) {
-            mPowerHintSession->close();
-            mPowerHintSession = nullptr;
-        }
-    };
-
-    static std::unique_ptr<HalWrapper> connect() {
-        // This only waits if the service is actually declared
-        sp<IPower> powerHal = waitForVintfService<IPower>();
-        if (powerHal == nullptr) {
-            return nullptr;
-        }
-        ALOGI("Loaded AIDL Power HAL service");
-
-        return std::make_unique<AidlPowerHalWrapper>(std::move(powerHal));
+    ret = mPowerHal->isBoostSupported(Boost::DISPLAY_UPDATE_IMMINENT, &mHasDisplayUpdateImminent);
+    if (!ret.isOk()) {
+        mHasDisplayUpdateImminent = false;
     }
 
-    bool setExpensiveRendering(bool enabled) override {
-        ALOGV("AIDL setExpensiveRendering %s", enabled ? "T" : "F");
-        if (!mHasExpensiveRendering) {
-            ALOGV("Skipped sending EXPENSIVE_RENDERING because HAL doesn't support it");
-            return true;
-        }
+    mSupportsPowerHint = checkPowerHintSessionSupported();
+}
 
-        auto ret = mPowerHal->setMode(Mode::EXPENSIVE_RENDERING, enabled);
-        if (ret.isOk()) {
-            traceExpensiveRendering(enabled);
-        }
-        return ret.isOk();
+AidlPowerHalWrapper::~AidlPowerHalWrapper() {
+    if (mPowerHintSession != nullptr) {
+        mPowerHintSession->close();
+        mPowerHintSession = nullptr;
     }
-
-    bool notifyDisplayUpdateImminent() override {
-        ALOGV("AIDL notifyDisplayUpdateImminent");
-        if (!mHasDisplayUpdateImminent) {
-            ALOGV("Skipped sending DISPLAY_UPDATE_IMMINENT because HAL doesn't support it");
-            return true;
-        }
-
-        auto ret = mPowerHal->setBoost(Boost::DISPLAY_UPDATE_IMMINENT, 0);
-        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;
 };
 
+std::unique_ptr<PowerAdvisor::HalWrapper> AidlPowerHalWrapper::connect() {
+    // This only waits if the service is actually declared
+    sp<IPower> powerHal = waitForVintfService<IPower>();
+    if (powerHal == nullptr) {
+        return nullptr;
+    }
+    ALOGI("Loaded AIDL Power HAL service");
+
+    return std::make_unique<AidlPowerHalWrapper>(std::move(powerHal));
+}
+
+bool AidlPowerHalWrapper::setExpensiveRendering(bool enabled) {
+    ALOGV("AIDL setExpensiveRendering %s", enabled ? "T" : "F");
+    if (!mHasExpensiveRendering) {
+        ALOGV("Skipped sending EXPENSIVE_RENDERING because HAL doesn't support it");
+        return true;
+    }
+
+    auto ret = mPowerHal->setMode(Mode::EXPENSIVE_RENDERING, enabled);
+    if (ret.isOk()) {
+        traceExpensiveRendering(enabled);
+    }
+    return ret.isOk();
+}
+
+bool AidlPowerHalWrapper::notifyDisplayUpdateImminent() {
+    ALOGV("AIDL notifyDisplayUpdateImminent");
+    if (!mHasDisplayUpdateImminent) {
+        ALOGV("Skipped sending DISPLAY_UPDATE_IMMINENT because HAL doesn't support it");
+        return true;
+    }
+
+    auto ret = mPowerHal->setBoost(Boost::DISPLAY_UPDATE_IMMINENT, 0);
+    return ret.isOk();
+}
+
+// only version 2+ of the aidl supports power hint sessions, hidl has no support
+bool AidlPowerHalWrapper::supportsPowerHintSession() {
+    return mSupportsPowerHint;
+}
+
+bool AidlPowerHalWrapper::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 together errors
+    return mPowerHal->getHintSessionPreferredRate(&unused).isOk();
+}
+
+bool AidlPowerHalWrapper::isPowerHintSessionRunning() {
+    return mPowerHintSession != nullptr;
+}
+
+void AidlPowerHalWrapper::closePowerHintSession() {
+    if (mPowerHintSession != nullptr) {
+        mPowerHintSession->close();
+        mPowerHintSession = nullptr;
+    }
+}
+
+void AidlPowerHalWrapper::restartPowerHintSession() {
+    closePowerHintSession();
+    startPowerHintSession();
+}
+
+void AidlPowerHalWrapper::setPowerHintSessionThreadIds(const std::vector<int32_t>& threadIds) {
+    if (threadIds != mPowerHintThreadIds) {
+        mPowerHintThreadIds = threadIds;
+        if (isPowerHintSessionRunning()) {
+            restartPowerHintSession();
+        }
+    }
+}
+
+bool AidlPowerHalWrapper::startPowerHintSession() {
+    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 AidlPowerHalWrapper::shouldSetTargetDuration(int64_t targetDurationNanos) {
+    if (targetDurationNanos <= 0) {
+        return false;
+    }
+    // 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 AidlPowerHalWrapper::setTargetWorkDuration(int64_t targetDurationNanos) {
+    ATRACE_CALL();
+    mTargetDuration = targetDurationNanos;
+    if (sTraceHintSessionData) ATRACE_INT64("Time target", targetDurationNanos);
+    if (!sNormalizeTarget && isPowerHintSessionRunning() &&
+        shouldSetTargetDuration(targetDurationNanos)) {
+        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: %" PRId64 "ns", 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 AidlPowerHalWrapper::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 AidlPowerHalWrapper::sendActualWorkDuration(int64_t actualDurationNanos,
+                                                 nsecs_t timeStampNanos) {
+    ATRACE_CALL();
+
+    if (actualDurationNanos < 0 || !isPowerHintSessionRunning()) {
+        ALOGV("Failed to send actual work duration, skipping");
+        return;
+    }
+    nsecs_t reportedDuration = actualDurationNanos;
+
+    // normalize the sent values to a pre-set target
+    if (sNormalizeTarget) {
+        reportedDuration += mLastTargetDurationSent - mTargetDuration;
+    } else {
+        // when target duration change is within deviation and not updated, adjust the actual
+        // duration proportionally based on the difference, e.g. if new target is 5ms longer than
+        // last reported but actual duration is the same as last target, we want to report a smaller
+        // actual work duration now to indicate that we are overshooting
+        if (mLastTargetDurationSent != kDefaultTarget.count() && mTargetDuration != 0) {
+            reportedDuration =
+                    static_cast<int64_t>(static_cast<long double>(mLastTargetDurationSent) /
+                                         mTargetDuration * actualDurationNanos);
+            mActualDuration = reportedDuration;
+        }
+    }
+    mActualDuration = reportedDuration;
+    WorkDuration duration;
+    duration.durationNanos = reportedDuration;
+    duration.timeStampNanos = timeStampNanos;
+    mPowerHintQueue.push_back(duration);
+
+    if (sTraceHintSessionData) {
+        ATRACE_INT64("Measured duration", actualDurationNanos);
+        ATRACE_INT64("Target error term", mTargetDuration - actualDurationNanos);
+
+        ATRACE_INT64("Reported duration", reportedDuration);
+        ATRACE_INT64("Reported target", mLastTargetDurationSent);
+        ATRACE_INT64("Reported target error term", mLastTargetDurationSent - reportedDuration);
+    }
+
+    ALOGV("Sending actual work duration of: %" PRId64 " on reported target: %" PRId64
+          " with error: %" PRId64,
+          reportedDuration, mLastTargetDurationSent, mLastTargetDurationSent - reportedDuration);
+
+    // 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 = reportedDuration;
+    }
+}
+
+bool AidlPowerHalWrapper::shouldReconnectHAL() {
+    return mShouldReconnectHal;
+}
+
+std::vector<int32_t> AidlPowerHalWrapper::getPowerHintSessionThreadIds() {
+    return mPowerHintThreadIds;
+}
+
+std::optional<int64_t> AidlPowerHalWrapper::getTargetWorkDuration() {
+    return mTargetDuration;
+}
+
 const bool AidlPowerHalWrapper::sTraceHintSessionData =
         base::GetBoolProperty(std::string("debug.sf.trace_hint_sessions"), false);
 
@@ -582,6 +564,7 @@
         if (!wrapper->shouldReconnectHAL()) {
             return wrapper;
         }
+        ALOGD("Reconnecting Power HAL");
         sHalWrapper = nullptr;
     }
 
@@ -594,7 +577,9 @@
     // 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
+    } else {
+        ALOGD("Successfully connecting AIDL Power HAL");
+        // 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
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
index 0db56aa..61bb32b 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
@@ -22,6 +22,7 @@
 
 #include <utils/Mutex.h>
 
+#include <android/hardware/power/IPower.h>
 #include <ui/DisplayIdentification.h>
 #include "../Scheduler/OneShotTimer.h"
 
@@ -118,6 +119,69 @@
     scheduler::OneShotTimer mScreenUpdateTimer;
 };
 
+class AidlPowerHalWrapper : public PowerAdvisor::HalWrapper {
+public:
+    explicit AidlPowerHalWrapper(sp<hardware::power::IPower> powerHal);
+    ~AidlPowerHalWrapper() override;
+
+    static std::unique_ptr<HalWrapper> connect();
+
+    bool setExpensiveRendering(bool enabled) override;
+    bool notifyDisplayUpdateImminent() override;
+    bool supportsPowerHintSession() override;
+    bool isPowerHintSessionRunning() override;
+    void restartPowerHintSession() override;
+    void setPowerHintSessionThreadIds(const std::vector<int32_t>& threadIds) override;
+    bool startPowerHintSession() override;
+    void setTargetWorkDuration(int64_t targetDurationNanos) override;
+    void sendActualWorkDuration(int64_t actualDurationNanos, nsecs_t timeStampNanos) override;
+    bool shouldReconnectHAL() override;
+    std::vector<int32_t> getPowerHintSessionThreadIds() override;
+    std::optional<int64_t> getTargetWorkDuration() override;
+
+private:
+    bool checkPowerHintSessionSupported();
+    void closePowerHintSession();
+    bool shouldReportActualDurationsNow();
+    bool shouldSetTargetDuration(int64_t targetDurationNanos);
+
+    const sp<hardware::power::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<hardware::power::IPowerHintSession> mPowerHintSession = nullptr;
+    // Queue of actual durations saved to report
+    std::vector<hardware::power::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.1 = 10%)
+    static constexpr double kAllowedTargetDeviationPercent = 0.1;
+    // 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;
+};
+
 } // namespace impl
 } // namespace Hwc2
 } // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
index b4fb51f..3803a78 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
@@ -19,20 +19,20 @@
 #pragma clang diagnostic ignored "-Wconversion"
 
 // #define LOG_NDEBUG 0
-#include "VirtualDisplaySurface.h"
 
 #include <cinttypes>
 
-#include "HWComposer.h"
-#include "SurfaceFlinger.h"
-
-#include <ftl/Flags.h>
 #include <ftl/enum.h>
+#include <ftl/flags.h>
 #include <gui/BufferItem.h>
 #include <gui/BufferQueue.h>
 #include <gui/IProducerListener.h>
 #include <system/window.h>
 
+#include "HWComposer.h"
+#include "SurfaceFlinger.h"
+#include "VirtualDisplaySurface.h"
+
 #define VDS_LOGE(msg, ...) ALOGE("[%s] " msg, \
         mDisplayName.c_str(), ##__VA_ARGS__)
 #define VDS_LOGW_IF(cond, msg, ...) ALOGW_IF(cond, "[%s] " msg, \
@@ -657,7 +657,7 @@
 
 std::string VirtualDisplaySurface::toString(CompositionType type) {
     using namespace std::literals;
-    return type == CompositionType::Unknown ? "Unknown"s : Flags(type).string();
+    return type == CompositionType::Unknown ? "Unknown"s : ftl::Flags(type).string();
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
index 307da41..e21095a 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
@@ -89,6 +89,9 @@
     virtual void dumpAsString(String8& result) const;
     virtual void resizeBuffers(const ui::Size&) override;
     virtual const sp<Fence>& getClientTargetAcquireFence() const override;
+    // Virtual display surface needs to prepare the frame based on composition type. Skip
+    // any client composition prediction.
+    virtual bool supportsCompositionStrategyPrediction() const override { return false; };
 
 private:
     enum Source : size_t {
diff --git a/services/surfaceflinger/EffectLayer.cpp b/services/surfaceflinger/EffectLayer.cpp
index cc85352..e8c590e 100644
--- a/services/surfaceflinger/EffectLayer.cpp
+++ b/services/surfaceflinger/EffectLayer.cpp
@@ -114,7 +114,13 @@
 }
 
 sp<compositionengine::LayerFE> EffectLayer::getCompositionEngineLayerFE() const {
-    return asLayerFE();
+    // There's no need to get a CE Layer if the EffectLayer isn't going to draw anything. In that
+    // case, it acts more like a ContainerLayer so returning a null CE Layer makes more sense
+    if (hasSomethingToDraw()) {
+        return asLayerFE();
+    } else {
+        return nullptr;
+    }
 }
 
 compositionengine::LayerFECompositionState* EffectLayer::editCompositionState() {
diff --git a/services/surfaceflinger/FlagManager.cpp b/services/surfaceflinger/FlagManager.cpp
index bd3cf74..f8ad8f6 100644
--- a/services/surfaceflinger/FlagManager.cpp
+++ b/services/surfaceflinger/FlagManager.cpp
@@ -96,7 +96,8 @@
 }
 
 bool FlagManager::use_adpf_cpu_hint() const {
-    std::optional<bool> sysPropVal = std::nullopt;
+    std::optional<bool> sysPropVal =
+            doParse<bool>(base::GetProperty("debug.sf.enable_adpf_cpu_hint", "").c_str());
     return getValue("AdpfFeature__adpf_cpu_hint", sysPropVal, false);
 }
 
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index fa5a1c8..3a92ca4 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -37,6 +37,7 @@
 #include <cutils/native_handle.h>
 #include <cutils/properties.h>
 #include <ftl/enum.h>
+#include <ftl/fake_guard.h>
 #include <gui/BufferItem.h>
 #include <gui/LayerDebugInfo.h>
 #include <gui/Surface.h>
@@ -81,11 +82,13 @@
 constexpr int kDumpTableRowLength = 159;
 } // namespace
 
+using namespace ftl::flag_operators;
+
 using base::StringAppendF;
-using namespace android::flag_operators;
-using PresentState = frametimeline::SurfaceFrame::PresentState;
 using gui::WindowInfo;
 
+using PresentState = frametimeline::SurfaceFrame::PresentState;
+
 std::atomic<int32_t> Layer::sSequence{1};
 
 Layer::Layer(const LayerCreationArgs& args)
@@ -139,6 +142,7 @@
     mDrawingState.destinationFrame.makeInvalid();
     mDrawingState.isTrustedOverlay = false;
     mDrawingState.dropInputMode = gui::DropInputMode::NONE;
+    mDrawingState.dimmingEnabled = true;
 
     if (args.flags & ISurfaceComposerClient::eNoColorFill) {
         // Set an invalid color so there is no color fill.
@@ -477,6 +481,7 @@
     compositionState->colorTransformIsIdentity = !hasColorTransform();
     compositionState->surfaceDamage = surfaceDamageRegion;
     compositionState->hasProtectedContent = isProtected();
+    compositionState->dimmingEnabled = isDimmingEnabled();
 
     const bool usesRoundedCorners = getRoundedCornerState().radius != 0.f;
 
@@ -1038,6 +1043,16 @@
     return true;
 }
 
+bool Layer::setDimmingEnabled(const bool dimmingEnabled) {
+    if (mDrawingState.dimmingEnabled == dimmingEnabled) return false;
+
+    mDrawingState.sequence++;
+    mDrawingState.dimmingEnabled = dimmingEnabled;
+    mDrawingState.modified = true;
+    setTransactionFlags(eTransactionNeeded);
+    return true;
+}
+
 bool Layer::setFrameRateSelectionPriority(int32_t priority) {
     if (mDrawingState.frameRateSelectionPriority == priority) return false;
     mDrawingState.frameRateSelectionPriority = priority;
@@ -2045,10 +2060,10 @@
     writeToProtoCommonState(layerProto, LayerVector::StateSet::Drawing, traceFlags);
 
     if (traceFlags & LayerTracing::TRACE_COMPOSITION) {
+        ftl::FakeGuard guard(mFlinger->mStateLock); // Called from the main thread.
+
         // Only populate for the primary display.
-        UnnecessaryLock assumeLocked(mFlinger->mStateLock); // called from the main thread.
-        const auto display = mFlinger->getDefaultDisplayDeviceLocked();
-        if (display) {
+        if (const auto display = mFlinger->getDefaultDisplayDeviceLocked()) {
             const auto compositionType = getCompositionType(*display);
             layerProto->set_hwc_composition_type(static_cast<HwcCompositionType>(compositionType));
             LayerProtoHelper::writeToProto(getVisibleRegion(display.get()),
@@ -2170,7 +2185,7 @@
 
     layerInfo->set_owner_uid(mOwnerUid);
 
-    if (traceFlags & LayerTracing::TRACE_INPUT) {
+    if ((traceFlags & LayerTracing::TRACE_INPUT) && needsInputInfo()) {
         WindowInfo info;
         if (useDrawing) {
             info = fillInputInfo(ui::Transform(), /* displayIsSecure */ true);
@@ -2581,6 +2596,8 @@
             return FrameRateCompatibility::ExactOrMultiple;
         case ANATIVEWINDOW_FRAME_RATE_EXACT:
             return FrameRateCompatibility::Exact;
+        case ANATIVEWINDOW_FRAME_RATE_NO_VOTE:
+            return FrameRateCompatibility::NoVote;
         default:
             LOG_ALWAYS_FATAL("Invalid frame rate compatibility value %d", compatibility);
             return FrameRateCompatibility::Default;
@@ -2647,6 +2664,18 @@
     mDrawingState.callbackHandles = {};
 }
 
+bool Layer::setTransactionCompletedListeners(const std::vector<sp<CallbackHandle>>& handles) {
+    if (handles.empty()) {
+        return false;
+    }
+
+    for (const auto& handle : handles) {
+        mFlinger->getTransactionCallbackInvoker().registerUnpresentedCallbackHandle(handle);
+    }
+
+    return true;
+}
+
 // ---------------------------------------------------------------------------
 
 std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate) {
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 846460d..ecea744 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -281,6 +281,8 @@
         gui::DropInputMode dropInputMode;
 
         bool autoRefresh = false;
+
+        bool dimmingEnabled = true;
     };
 
     /*
@@ -411,6 +413,7 @@
     virtual mat4 getColorTransform() const;
     virtual bool hasColorTransform() const;
     virtual bool isColorSpaceAgnostic() const { return mDrawingState.colorSpaceAgnostic; }
+    virtual bool isDimmingEnabled() const { return getDrawingState().dimmingEnabled; };
 
     // Used only to set BufferStateLayer state
     virtual bool setTransform(uint32_t /*transform*/) { return false; };
@@ -428,15 +431,14 @@
     virtual bool setApi(int32_t /*api*/) { return false; };
     virtual bool setSidebandStream(const sp<NativeHandle>& /*sidebandStream*/) { return false; };
     virtual bool setTransactionCompletedListeners(
-            const std::vector<sp<CallbackHandle>>& /*handles*/) {
-        return false;
-    };
+            const std::vector<sp<CallbackHandle>>& /*handles*/);
     virtual bool addFrameEvent(const sp<Fence>& /*acquireFence*/, nsecs_t /*postedTime*/,
                                nsecs_t /*requestedPresentTime*/) {
         return false;
     }
     virtual bool setBackgroundColor(const half3& color, float alpha, ui::Dataspace dataspace);
     virtual bool setColorSpaceAgnostic(const bool agnostic);
+    virtual bool setDimmingEnabled(const bool dimmingEnabled);
     virtual bool setFrameRateSelectionPriority(int32_t priority);
     virtual bool setFixedTransformHint(ui::Transform::RotationFlags fixedTransformHint);
     virtual void setAutoRefresh(bool /* autoRefresh */) {}
diff --git a/services/surfaceflinger/MainThreadGuard.h b/services/surfaceflinger/MainThreadGuard.h
deleted file mode 100644
index c1aa118..0000000
--- a/services/surfaceflinger/MainThreadGuard.h
+++ /dev/null
@@ -1,35 +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.
- */
-
-#pragma once
-
-#include <utils/Mutex.h>
-
-namespace android {
-namespace {
-
-// Helps to ensure that some functions runs on SF's main thread by using the
-// clang thread safety annotations.
-class CAPABILITY("mutex") MainThreadGuard {
-} SF_MAIN_THREAD;
-
-struct SCOPED_CAPABILITY MainThreadScopedGuard {
-public:
-    explicit MainThreadScopedGuard(MainThreadGuard& mutex) ACQUIRE(mutex) {}
-    ~MainThreadScopedGuard() RELEASE() {}
-};
-} // namespace
-} // namespace android
diff --git a/services/surfaceflinger/MutexUtils.h b/services/surfaceflinger/MutexUtils.h
new file mode 100644
index 0000000..f8be6f3
--- /dev/null
+++ b/services/surfaceflinger/MutexUtils.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <log/log.h>
+#include <utils/Mutex.h>
+
+namespace android {
+
+struct SCOPED_CAPABILITY ConditionalLock {
+    ConditionalLock(Mutex& mutex, bool lock) ACQUIRE(mutex) : mutex(mutex), lock(lock) {
+        if (lock) mutex.lock();
+    }
+
+    ~ConditionalLock() RELEASE() {
+        if (lock) mutex.unlock();
+    }
+
+    Mutex& mutex;
+    const bool lock;
+};
+
+struct SCOPED_CAPABILITY TimedLock {
+    TimedLock(Mutex& mutex, nsecs_t timeout, const char* whence) ACQUIRE(mutex)
+          : mutex(mutex), status(mutex.timedLock(timeout)) {
+        ALOGE_IF(!locked(), "%s timed out locking: %s (%d)", whence, strerror(-status), status);
+    }
+
+    ~TimedLock() RELEASE() {
+        if (locked()) mutex.unlock();
+    }
+
+    bool locked() const { return status == NO_ERROR; }
+
+    Mutex& mutex;
+    const status_t status;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index 81c1566..d4435c2 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -14,22 +14,20 @@
  * 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"
-
 #include <algorithm>
 
 #include "RefreshRateOverlay.h"
 #include "Client.h"
 #include "Layer.h"
 
-#include <SkBlendMode.h>
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+#include <SkCanvas.h>
 #include <SkPaint.h>
+#pragma clang diagnostic pop
+#include <SkBlendMode.h>
 #include <SkRect.h>
 #include <SkSurface.h>
-#include <gui/IProducerListener.h>
 #include <gui/SurfaceComposerClient.h>
 #include <gui/SurfaceControl.h>
 
@@ -37,29 +35,49 @@
 #define LOG_TAG "RefreshRateOverlay"
 
 namespace android {
+namespace {
 
-void RefreshRateOverlay::SevenSegmentDrawer::drawSegment(Segment segment, int left, SkColor& color,
+constexpr int kDigitWidth = 64;
+constexpr int kDigitHeight = 100;
+constexpr int kDigitSpace = 16;
+
+// Layout is digit, space, digit, space, digit, space, spinner.
+constexpr int kBufferWidth = 4 * kDigitWidth + 3 * kDigitSpace;
+constexpr int kBufferHeight = kDigitHeight;
+
+SurfaceComposerClient::Transaction createTransaction(const sp<SurfaceControl>& surface) {
+    constexpr float kFrameRate = 0.f;
+    constexpr int8_t kCompatibility = ANATIVEWINDOW_FRAME_RATE_NO_VOTE;
+    constexpr int8_t kSeamlessness = ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS;
+
+    return SurfaceComposerClient::Transaction().setFrameRate(surface, kFrameRate, kCompatibility,
+                                                             kSeamlessness);
+}
+
+} // namespace
+
+void RefreshRateOverlay::SevenSegmentDrawer::drawSegment(Segment segment, int left, SkColor color,
                                                          SkCanvas& canvas) {
     const SkRect rect = [&]() {
         switch (segment) {
             case Segment::Upper:
-                return SkRect::MakeLTRB(left, 0, left + DIGIT_WIDTH, DIGIT_SPACE);
+                return SkRect::MakeLTRB(left, 0, left + kDigitWidth, kDigitSpace);
             case Segment::UpperLeft:
-                return SkRect::MakeLTRB(left, 0, left + DIGIT_SPACE, DIGIT_HEIGHT / 2);
+                return SkRect::MakeLTRB(left, 0, left + kDigitSpace, kDigitHeight / 2);
             case Segment::UpperRight:
-                return SkRect::MakeLTRB(left + DIGIT_WIDTH - DIGIT_SPACE, 0, left + DIGIT_WIDTH,
-                                        DIGIT_HEIGHT / 2);
+                return SkRect::MakeLTRB(left + kDigitWidth - kDigitSpace, 0, left + kDigitWidth,
+                                        kDigitHeight / 2);
             case Segment::Middle:
-                return SkRect::MakeLTRB(left, DIGIT_HEIGHT / 2 - DIGIT_SPACE / 2,
-                                        left + DIGIT_WIDTH, DIGIT_HEIGHT / 2 + DIGIT_SPACE / 2);
+                return SkRect::MakeLTRB(left, kDigitHeight / 2 - kDigitSpace / 2,
+                                        left + kDigitWidth, kDigitHeight / 2 + kDigitSpace / 2);
             case Segment::LowerLeft:
-                return SkRect::MakeLTRB(left, DIGIT_HEIGHT / 2, left + DIGIT_SPACE, DIGIT_HEIGHT);
+                return SkRect::MakeLTRB(left, kDigitHeight / 2, left + kDigitSpace, kDigitHeight);
             case Segment::LowerRight:
-                return SkRect::MakeLTRB(left + DIGIT_WIDTH - DIGIT_SPACE, DIGIT_HEIGHT / 2,
-                                        left + DIGIT_WIDTH, DIGIT_HEIGHT);
+                return SkRect::MakeLTRB(left + kDigitWidth - kDigitSpace, kDigitHeight / 2,
+                                        left + kDigitWidth, kDigitHeight);
             case Segment::Bottom:
-                return SkRect::MakeLTRB(left, DIGIT_HEIGHT - DIGIT_SPACE, left + DIGIT_WIDTH,
-                                        DIGIT_HEIGHT);
+                return SkRect::MakeLTRB(left, kDigitHeight - kDigitSpace, left + kDigitWidth,
+                                        kDigitHeight);
         }
     }();
 
@@ -69,7 +87,7 @@
     canvas.drawRect(rect, paint);
 }
 
-void RefreshRateOverlay::SevenSegmentDrawer::drawDigit(int digit, int left, SkColor& color,
+void RefreshRateOverlay::SevenSegmentDrawer::drawDigit(int digit, int left, SkColor color,
                                                        SkCanvas& canvas) {
     if (digit < 0 || digit > 9) return;
 
@@ -94,37 +112,45 @@
         drawSegment(Segment::Bottom, left, color, canvas);
 }
 
-std::vector<sp<GraphicBuffer>> RefreshRateOverlay::SevenSegmentDrawer::draw(
-        int number, SkColor& color, ui::Transform::RotationFlags rotation, bool showSpinner) {
+auto RefreshRateOverlay::SevenSegmentDrawer::draw(int number, SkColor color,
+                                                  ui::Transform::RotationFlags rotation,
+                                                  bool showSpinner) -> Buffers {
     if (number < 0 || number > 1000) return {};
 
     const auto hundreds = number / 100;
     const auto tens = (number / 10) % 10;
     const auto ones = number % 10;
 
-    std::vector<sp<GraphicBuffer>> buffers;
-    const auto loopCount = showSpinner ? 6 : 1;
-    for (int i = 0; i < loopCount; i++) {
+    const size_t loopCount = showSpinner ? 6 : 1;
+
+    Buffers buffers;
+    buffers.reserve(loopCount);
+
+    for (size_t i = 0; i < loopCount; i++) {
         // Pre-rotate the buffer before it reaches SurfaceFlinger.
         SkMatrix canvasTransform = SkMatrix();
-        auto [bufferWidth, bufferHeight] = [&] {
+        const auto [bufferWidth, bufferHeight] = [&]() -> std::pair<int, int> {
             switch (rotation) {
                 case ui::Transform::ROT_90:
-                    canvasTransform.setTranslate(BUFFER_HEIGHT, 0);
-                    canvasTransform.preRotate(90);
-                    return std::make_tuple(BUFFER_HEIGHT, BUFFER_WIDTH);
+                    canvasTransform.setTranslate(kBufferHeight, 0);
+                    canvasTransform.preRotate(90.f);
+                    return {kBufferHeight, kBufferWidth};
                 case ui::Transform::ROT_270:
-                    canvasTransform.setRotate(270, BUFFER_WIDTH / 2.0, BUFFER_WIDTH / 2.0);
-                    return std::make_tuple(BUFFER_HEIGHT, BUFFER_WIDTH);
+                    canvasTransform.setRotate(270.f, kBufferWidth / 2.f, kBufferWidth / 2.f);
+                    return {kBufferHeight, kBufferWidth};
                 default:
-                    return std::make_tuple(BUFFER_WIDTH, BUFFER_HEIGHT);
+                    return {kBufferWidth, kBufferHeight};
             }
         }();
+
         sp<GraphicBuffer> buffer =
-                new GraphicBuffer(bufferWidth, bufferHeight, HAL_PIXEL_FORMAT_RGBA_8888, 1,
+                new GraphicBuffer(static_cast<uint32_t>(bufferWidth),
+                                  static_cast<uint32_t>(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);
@@ -137,15 +163,15 @@
         if (hundreds != 0) {
             drawDigit(hundreds, left, color, *canvas);
         }
-        left += DIGIT_WIDTH + DIGIT_SPACE;
+        left += kDigitWidth + kDigitSpace;
 
         if (tens != 0) {
             drawDigit(tens, left, color, *canvas);
         }
-        left += DIGIT_WIDTH + DIGIT_SPACE;
+        left += kDigitWidth + kDigitSpace;
 
         drawDigit(ones, left, color, *canvas);
-        left += DIGIT_WIDTH + DIGIT_SPACE;
+        left += kDigitWidth + kDigitSpace;
 
         if (showSpinner) {
             switch (i) {
@@ -172,51 +198,43 @@
 
         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();
+        const size_t dstRowBytes =
+                buffer->getStride() * static_cast<size_t>(imageInfo.bytesPerPixel());
+
         canvas->readPixels(imageInfo, pixels, dstRowBytes, 0, 0);
         buffer->unlock();
-        buffers.emplace_back(buffer);
+        buffers.push_back(std::move(buffer));
     }
     return buffers;
 }
 
-RefreshRateOverlay::RefreshRateOverlay(SurfaceFlinger& flinger, uint32_t lowFps, uint32_t highFps,
-                                       bool showSpinner)
-      : mFlinger(flinger),
-        mClient(new Client(&mFlinger)),
+RefreshRateOverlay::RefreshRateOverlay(FpsRange fpsRange, bool showSpinner)
+      : mFpsRange(fpsRange),
         mShowSpinner(showSpinner),
-        mLowFps(lowFps),
-        mHighFps(highFps) {
-    createLayer();
-}
-
-bool RefreshRateOverlay::createLayer() {
-    mSurfaceControl =
-            SurfaceComposerClient::getDefault()
-                    ->createSurface(String8("RefreshRateOverlay"), SevenSegmentDrawer::getWidth(),
-                                    SevenSegmentDrawer::getHeight(), PIXEL_FORMAT_RGBA_8888,
-                                    ISurfaceComposerClient::eFXSurfaceBufferState);
-
+        mSurfaceControl(SurfaceComposerClient::getDefault()
+                                ->createSurface(String8("RefreshRateOverlay"), kBufferWidth,
+                                                kBufferHeight, PIXEL_FORMAT_RGBA_8888,
+                                                ISurfaceComposerClient::eFXSurfaceBufferState)) {
     if (!mSurfaceControl) {
-        ALOGE("failed to create buffer state layer");
-        return false;
+        ALOGE("%s: Failed to create buffer state layer", __func__);
+        return;
     }
 
-    SurfaceComposerClient::Transaction()
-            .setFrameRate(mSurfaceControl, 0.0f,
-                          static_cast<int8_t>(Layer::FrameRateCompatibility::NoVote),
-                          static_cast<int8_t>(scheduler::Seamlessness::OnlySeamless))
+    createTransaction(mSurfaceControl)
             .setLayer(mSurfaceControl, INT32_MAX - 2)
             .setTrustedOverlay(mSurfaceControl, true)
             .apply();
-
-    return true;
 }
 
-const std::vector<sp<GraphicBuffer>>& RefreshRateOverlay::getOrCreateBuffers(uint32_t fps) {
-    ui::Transform::RotationFlags transformHint =
+auto RefreshRateOverlay::getOrCreateBuffers(Fps fps) -> const Buffers& {
+    static const Buffers kNoBuffers;
+    if (!mSurfaceControl) return kNoBuffers;
+
+    const auto transformHint =
             static_cast<ui::Transform::RotationFlags>(mSurfaceControl->getTransformHint());
+
     // Tell SurfaceFlinger about the pre-rotation on the buffer.
     const auto transform = [&] {
         switch (transformHint) {
@@ -229,74 +247,72 @@
         }
     }();
 
-    SurfaceComposerClient::Transaction t;
-    t.setTransform(mSurfaceControl, transform);
-    t.apply();
+    createTransaction(mSurfaceControl).setTransform(mSurfaceControl, transform).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
-        // of this range if the display has changed its set of supported refresh rates.
-        fps = std::max(fps, mLowFps);
-        fps = std::min(fps, mHighFps);
-        const auto fpsScale = static_cast<float>(fps - mLowFps) / rangeLength;
-        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);
+    BufferCache::const_iterator it = mBufferCache.find({fps.getIntValue(), transformHint});
+    if (it == mBufferCache.end()) {
+        const int minFps = mFpsRange.min.getIntValue();
+        const int maxFps = mFpsRange.max.getIntValue();
+
+        // Clamp to the range. The current fps may be outside of this range if the display has
+        // changed its set of supported refresh rates.
+        const int intFps = std::clamp(fps.getIntValue(), minFps, maxFps);
+
+        // Ensure non-zero range to avoid division by zero.
+        const float fpsScale = static_cast<float>(intFps - minFps) / std::max(1, maxFps - minFps);
+
+        constexpr SkColor kMinFpsColor = SK_ColorRED;
+        constexpr SkColor kMaxFpsColor = SK_ColorGREEN;
+        constexpr float kAlpha = 0.8f;
+
+        SkColor4f colorBase = SkColor4f::FromColor(kMaxFpsColor) * fpsScale;
+        const SkColor4f minFpsColor = SkColor4f::FromColor(kMinFpsColor) * (1 - fpsScale);
+
+        colorBase.fR = colorBase.fR + minFpsColor.fR;
+        colorBase.fG = colorBase.fG + minFpsColor.fG;
+        colorBase.fB = colorBase.fB + minFpsColor.fB;
+        colorBase.fA = kAlpha;
+
+        const SkColor color = colorBase.toSkColor();
+
+        auto buffers = SevenSegmentDrawer::draw(intFps, color, transformHint, mShowSpinner);
+        it = mBufferCache.try_emplace({intFps, transformHint}, std::move(buffers)).first;
     }
 
-    return mBufferCache[transformHint][fps];
+    return it->second;
 }
 
 void RefreshRateOverlay::setViewport(ui::Size viewport) {
     constexpr int32_t kMaxWidth = 1000;
-    const auto width = std::min(kMaxWidth, std::min(viewport.width, viewport.height));
+    const auto width = std::min({kMaxWidth, viewport.width, viewport.height});
     const auto height = 2 * width;
     Rect frame((3 * width) >> 4, height >> 5);
     frame.offsetBy(width >> 5, height >> 4);
 
-    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();
+    createTransaction(mSurfaceControl)
+            .setMatrix(mSurfaceControl, frame.getWidth() / static_cast<float>(kBufferWidth), 0, 0,
+                       frame.getHeight() / static_cast<float>(kBufferHeight))
+            .setPosition(mSurfaceControl, frame.left, frame.top)
+            .apply();
 }
 
 void RefreshRateOverlay::setLayerStack(ui::LayerStack stack) {
-    SurfaceComposerClient::Transaction t;
-    t.setLayerStack(mSurfaceControl, stack);
-    t.apply();
+    createTransaction(mSurfaceControl).setLayerStack(mSurfaceControl, stack).apply();
 }
 
 void RefreshRateOverlay::changeRefreshRate(Fps fps) {
-    mCurrentFps = fps.getIntValue();
-    auto buffer = getOrCreateBuffers(*mCurrentFps)[mFrame];
-    SurfaceComposerClient::Transaction t;
-    t.setBuffer(mSurfaceControl, buffer);
-    t.apply();
+    mCurrentFps = fps;
+    const auto buffer = getOrCreateBuffers(fps)[mFrame];
+    createTransaction(mSurfaceControl).setBuffer(mSurfaceControl, buffer).apply();
 }
 
 void RefreshRateOverlay::animate() {
-    if (!mCurrentFps.has_value()) return;
+    if (!mShowSpinner || !mCurrentFps) return;
 
     const auto& buffers = getOrCreateBuffers(*mCurrentFps);
     mFrame = (mFrame + 1) % buffers.size();
-    auto buffer = buffers[mFrame];
-    SurfaceComposerClient::Transaction t;
-    t.setBuffer(mSurfaceControl, buffer);
-    t.apply();
+    const auto buffer = buffers[mFrame];
+    createTransaction(mSurfaceControl).setBuffer(mSurfaceControl, buffer).apply();
 }
 
 } // 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/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h
index 381df37..a465a36 100644
--- a/services/surfaceflinger/RefreshRateOverlay.h
+++ b/services/surfaceflinger/RefreshRateOverlay.h
@@ -16,32 +16,27 @@
 
 #pragma once
 
-#include <SkCanvas.h>
 #include <SkColor.h>
-#include <unordered_map>
+#include <vector>
 
-#include <math/vec4.h>
-#include <renderengine/RenderEngine.h>
+#include <ftl/small_map.h>
 #include <ui/LayerStack.h>
-#include <ui/Rect.h>
 #include <ui/Size.h>
+#include <ui/Transform.h>
 #include <utils/StrongPointer.h>
 
 #include <scheduler/Fps.h>
 
+class SkCanvas;
+
 namespace android {
 
-class Client;
 class GraphicBuffer;
-class IBinder;
-class IGraphicBufferProducer;
-class Layer;
-class SurfaceFlinger;
 class SurfaceControl;
 
 class RefreshRateOverlay {
 public:
-    RefreshRateOverlay(SurfaceFlinger&, uint32_t lowFps, uint32_t highFps, bool showSpinner);
+    RefreshRateOverlay(FpsRange, bool showSpinner);
 
     void setLayerStack(ui::LayerStack);
     void setViewport(ui::Size);
@@ -49,52 +44,38 @@
     void animate();
 
 private:
+    using Buffers = std::vector<sp<GraphicBuffer>>;
+
     class SevenSegmentDrawer {
     public:
-        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; }
+        static Buffers draw(int number, SkColor, ui::Transform::RotationFlags, bool showSpinner);
 
     private:
         enum class Segment { Upper, UpperLeft, UpperRight, Middle, LowerLeft, LowerRight, Bottom };
 
-        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;
-        static constexpr uint32_t DIGIT_SPACE = 16;
-        static constexpr uint32_t BUFFER_HEIGHT = DIGIT_HEIGHT;
-        static constexpr uint32_t BUFFER_WIDTH =
-                4 * DIGIT_WIDTH + 3 * DIGIT_SPACE; // Digit|Space|Digit|Space|Digit|Space|Spinner
+        static void drawSegment(Segment, int left, SkColor, SkCanvas&);
+        static void drawDigit(int digit, int left, SkColor, SkCanvas&);
     };
 
-    bool createLayer();
+    const Buffers& getOrCreateBuffers(Fps);
 
-    const std::vector<sp<GraphicBuffer>>& getOrCreateBuffers(uint32_t fps);
+    struct Key {
+        int fps;
+        ui::Transform::RotationFlags flags;
 
-    SurfaceFlinger& mFlinger;
-    const sp<Client> mClient;
-    sp<IBinder> mIBinder;
-    sp<IGraphicBufferProducer> mGbp;
+        bool operator==(Key other) const { return fps == other.fps && flags == other.flags; }
+    };
 
-    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 SkColor LOW_FPS_COLOR = SK_ColorRED;
-    const SkColor HIGH_FPS_COLOR = SK_ColorGREEN;
+    using BufferCache = ftl::SmallMap<Key, Buffers, 9>;
+    BufferCache mBufferCache;
 
+    std::optional<Fps> mCurrentFps;
+    size_t mFrame = 0;
+
+    const FpsRange mFpsRange; // For color interpolation.
     const bool mShowSpinner;
 
-    // Interpolate the colors between these values.
-    const uint32_t mLowFps;
-    const uint32_t mHighFps;
-
-    sp<SurfaceControl> mSurfaceControl;
+    const sp<SurfaceControl> mSurfaceControl;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 5ba8a1b..cbea77e 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -346,6 +346,12 @@
 
 VsyncEventData EventThread::getLatestVsyncEventData(
         const sp<EventThreadConnection>& connection) const {
+    // Resync so that the vsync is accurate with hardware. getLatestVsyncEventData is an alternate
+    // way to get vsync data (instead of posting callbacks to Choreographer).
+    if (connection->resyncCallback) {
+        connection->resyncCallback();
+    }
+
     VsyncEventData vsyncEventData;
     nsecs_t frameInterval = mGetVsyncPeriodFunction(connection->mOwnerUid);
     vsyncEventData.frameInterval = frameInterval;
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 3226f22..ca83496 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -133,9 +133,9 @@
 } // 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,
+    return base::StringPrintf("{defaultModeId=%d, allowGroupSwitching=%s"
+                              ", primaryRange=%s, appRequestRange=%s}",
+                              defaultMode.value(), allowGroupSwitching ? "true" : "false",
                               to_string(primaryRange).c_str(), to_string(appRequestRange).c_str());
 }
 
@@ -922,41 +922,46 @@
 }
 
 void RefreshRateConfigs::dump(std::string& result) const {
+    using namespace std::string_literals;
+
     std::lock_guard lock(mLock);
-    base::StringAppendF(&result, "DesiredDisplayModeSpecs (DisplayManager): %s\n\n",
-                        mDisplayManagerPolicy.toString().c_str());
-    scheduler::RefreshRateConfigs::Policy currentPolicy = *getCurrentPolicyLocked();
-    if (mOverridePolicy && currentPolicy != mDisplayManagerPolicy) {
-        base::StringAppendF(&result, "DesiredDisplayModeSpecs (Override): %s\n\n",
-                            currentPolicy.toString().c_str());
-    }
 
-    base::StringAppendF(&result, "Active mode: %s\n", to_string(*mActiveModeIt->second).c_str());
+    const auto activeModeId = mActiveModeIt->first;
+    result += "   activeModeId="s;
+    result += std::to_string(activeModeId.value());
 
-    result.append("Display modes:\n");
+    result += "\n   displayModes=\n"s;
     for (const auto& [id, mode] : mDisplayModes) {
-        result.push_back('\t');
-        result.append(to_string(*mode));
-        result.push_back('\n');
+        result += "      "s;
+        result += to_string(*mode);
+        result += '\n';
     }
 
-    base::StringAppendF(&result, "Supports Frame Rate Override By Content: %s\n",
-                        mSupportsFrameRateOverrideByContent ? "yes" : "no");
+    base::StringAppendF(&result, "   displayManagerPolicy=%s\n",
+                        mDisplayManagerPolicy.toString().c_str());
 
-    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 (const Policy& currentPolicy = *getCurrentPolicyLocked();
+        mOverridePolicy && currentPolicy != mDisplayManagerPolicy) {
+        base::StringAppendF(&result, "   overridePolicy=%s\n", currentPolicy.toString().c_str());
     }
 
+    base::StringAppendF(&result, "   supportsFrameRateOverrideByContent=%s\n",
+                        mSupportsFrameRateOverrideByContent ? "true" : "false");
+
+    result += "   idleTimer="s;
     if (mIdleTimer) {
-        result.append(mIdleTimer->dump());
+        result += mIdleTimer->dump();
     } else {
-        result.append("off");
+        result += "off"s;
     }
 
-    result.append("\n\n");
+    if (const auto controller = mConfig.kernelIdleTimerController) {
+        base::StringAppendF(&result, " (kernel via %s)", ftl::enum_string(*controller).c_str());
+    } else {
+        result += " (platform)"s;
+    }
+
+    result += '\n';
 }
 
 std::chrono::milliseconds RefreshRateConfigs::getIdleTimerTimeout() {
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Features.h b/services/surfaceflinger/Scheduler/include/scheduler/Features.h
index 0e96678..b3a6a60 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Features.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Features.h
@@ -16,10 +16,10 @@
 
 #pragma once
 
-#include <ftl/Flags.h>
-
 #include <cstdint>
 
+#include <ftl/flags.h>
+
 namespace android::scheduler {
 
 enum class Feature : std::uint8_t {
@@ -29,6 +29,6 @@
     kTracePredictedVsync = 0b1000,
 };
 
-using FeatureFlags = Flags<Feature>;
+using FeatureFlags = ftl::Flags<Feature>;
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 9f5a7df..d39176b 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -24,7 +24,10 @@
 
 #include "SurfaceFlinger.h"
 
+#include <android-base/parseint.h>
 #include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 #include <android/configuration.h>
 #include <android/gui/IDisplayEventConnection.h>
 #include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
@@ -49,6 +52,7 @@
 #include <configstore/Utils.h>
 #include <cutils/compiler.h>
 #include <cutils/properties.h>
+#include <ftl/fake_guard.h>
 #include <ftl/future.h>
 #include <ftl/small_map.h>
 #include <gui/BufferQueue.h>
@@ -91,6 +95,7 @@
 #include <cmath>
 #include <cstdint>
 #include <functional>
+#include <memory>
 #include <mutex>
 #include <optional>
 #include <type_traits>
@@ -109,6 +114,7 @@
 #include "DisplayHardware/FramebufferSurface.h"
 #include "DisplayHardware/HWComposer.h"
 #include "DisplayHardware/Hal.h"
+#include "DisplayHardware/PowerAdvisor.h"
 #include "DisplayHardware/VirtualDisplaySurface.h"
 #include "DisplayRenderArea.h"
 #include "EffectLayer.h"
@@ -123,6 +129,7 @@
 #include "LayerRenderArea.h"
 #include "LayerVector.h"
 #include "MonitoredProducer.h"
+#include "MutexUtils.h"
 #include "NativeWindowSurface.h"
 #include "RefreshRateOverlay.h"
 #include "RegionSamplingThread.h"
@@ -138,49 +145,29 @@
 #include "TimeStats/TimeStats.h"
 #include "TunnelModeEnabledReporter.h"
 #include "WindowInfosListenerInvoker.h"
-#include "android-base/parseint.h"
-#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);                                             \
-    }()
+#include <aidl/android/hardware/graphics/composer3/RenderIntent.h>
 
 #undef NO_THREAD_SAFETY_ANALYSIS
 #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;
+    _Pragma("GCC error \"Prefer <ftl/fake_guard.h> or MutexUtils.h helpers.\"")
 
 namespace android {
 
 using namespace std::string_literals;
 
-using namespace android::hardware::configstore;
-using namespace android::hardware::configstore::V1_0;
-using namespace android::sysprop;
+using namespace hardware::configstore;
+using namespace hardware::configstore::V1_0;
+using namespace sysprop;
 
-using android::hardware::power::Boost;
+using aidl::android::hardware::graphics::common::DisplayDecorationSupport;
+using aidl::android::hardware::graphics::composer3::Capability;
+using aidl::android::hardware::graphics::composer3::DisplayCapability;
+using CompositionStrategyPredictionState = android::compositionengine::impl::
+        OutputCompositionState::CompositionStrategyPredictionState;
+
 using base::StringAppendF;
 using gui::DisplayInfo;
 using gui::IDisplayEventConnection;
@@ -191,6 +178,8 @@
 using ui::DisplayPrimaries;
 using ui::RenderIntent;
 
+using KernelIdleTimerController = scheduler::RefreshRateConfigs::KernelIdleTimerController;
+
 namespace hal = android::hardware::graphics::composer::hal;
 
 namespace {
@@ -222,38 +211,6 @@
 
 #pragma clang diagnostic pop
 
-template <typename Mutex>
-struct SCOPED_CAPABILITY ConditionalLockGuard {
-    ConditionalLockGuard(Mutex& mutex, bool lock) ACQUIRE(mutex) : mutex(mutex), lock(lock) {
-        if (lock) mutex.lock();
-    }
-
-    ~ConditionalLockGuard() RELEASE() {
-        if (lock) mutex.unlock();
-    }
-
-    Mutex& mutex;
-    const bool lock;
-};
-
-using ConditionalLock = ConditionalLockGuard<Mutex>;
-
-struct SCOPED_CAPABILITY TimedLock {
-    TimedLock(Mutex& mutex, nsecs_t timeout, const char* whence) ACQUIRE(mutex)
-          : mutex(mutex), status(mutex.timedLock(timeout)) {
-        ALOGE_IF(!locked(), "%s timed out locking: %s (%d)", whence, strerror(-status), status);
-    }
-
-    ~TimedLock() RELEASE() {
-        if (locked()) mutex.unlock();
-    }
-
-    bool locked() const { return status == NO_ERROR; }
-
-    Mutex& mutex;
-    const status_t status;
-};
-
 // TODO(b/141333600): Consolidate with DisplayMode::Builder::getDefaultDensity.
 constexpr float FALLBACK_DENSITY = ACONFIGURATION_DENSITY_TV;
 
@@ -373,7 +330,7 @@
         mTunnelModeEnabledReporter(new TunnelModeEnabledReporter()),
         mInternalDisplayDensity(getDensityFromProperty("ro.sf.lcd_density", true)),
         mEmulatedDisplayDensity(getDensityFromProperty("qemu.sf.lcd_density", false)),
-        mPowerAdvisor(*this),
+        mPowerAdvisor(std::make_unique<Hwc2::impl::PowerAdvisor>(*this)),
         mWindowInfosListenerInvoker(sp<WindowInfosListenerInvoker>::make(*this)) {
     ALOGI("Using HWComposer service: %s", mHwcServiceName.c_str());
 }
@@ -459,6 +416,9 @@
     property_get("debug.sf.disable_client_composition_cache", value, "0");
     mDisableClientCompositionCache = atoi(value);
 
+    property_get("debug.sf.predict_hwc_composition_strategy", value, "1");
+    mPredictCompositionStrategy = atoi(value);
+
     // We should be reading 'persist.sys.sf.color_saturation' here
     // but since /data may be encrypted, we need to wait until after vold
     // comes online to attempt to read the property. The property is
@@ -623,14 +583,8 @@
 std::vector<PhysicalDisplayId> SurfaceFlinger::getPhysicalDisplayIdsLocked() const {
     std::vector<PhysicalDisplayId> displayIds;
     displayIds.reserve(mPhysicalDisplayTokens.size());
-    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();
-    }();
+    const auto defaultDisplayId = getDefaultDisplayDeviceLocked()->getPhysicalId();
     displayIds.push_back(defaultDisplayId);
 
     for (const auto& [id, token] : mPhysicalDisplayTokens) {
@@ -644,7 +598,7 @@
 
 status_t SurfaceFlinger::getPrimaryPhysicalDisplayId(PhysicalDisplayId* id) const {
     Mutex::Autolock lock(mStateLock);
-    *id = getInternalDisplayIdLocked();
+    *id = getPrimaryDisplayIdLocked();
     return NO_ERROR;
 }
 
@@ -720,22 +674,28 @@
         }
 
         readPersistentProperties();
-        mPowerAdvisor.onBootFinished();
-        mPowerAdvisor.enablePowerHint(mFlagManager.use_adpf_cpu_hint());
-        if (mPowerAdvisor.usePowerHintSession()) {
+        mPowerAdvisor->onBootFinished();
+        const bool powerHintEnabled = mFlagManager.use_adpf_cpu_hint();
+        mPowerAdvisor->enablePowerHint(powerHintEnabled);
+        const bool powerHintUsed = mPowerAdvisor->usePowerHintSession();
+        ALOGD("Power hint is %s",
+              powerHintUsed ? "supported" : (powerHintEnabled ? "unsupported" : "disabled"));
+        if (powerHintUsed) {
             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);
+            if (!mPowerAdvisor->startPowerHintSession(tidList)) {
+                ALOGW("Cannot start power hint session");
+            }
         }
 
         mBootStage = BootStage::FINISHED;
 
         if (property_get_bool("sf.debug.show_refresh_rate_overlay", false)) {
-            ON_MAIN_THREAD(enableRefreshRateOverlay(true));
+            FTL_FAKE_GUARD(mStateLock, enableRefreshRateOverlay(true));
         }
     }));
 }
@@ -853,7 +813,7 @@
     // set initial conditions (e.g. unblank default device)
     initializeDisplays();
 
-    mPowerAdvisor.init();
+    mPowerAdvisor->init();
 
     char primeShaderCache[PROPERTY_VALUE_MAX];
     property_get("service.sf.prime_shader_cache", primeShaderCache, "1");
@@ -936,8 +896,9 @@
         FrameEvent::DEQUEUE_READY,
         FrameEvent::RELEASE,
     };
-    ConditionalLock _l(mStateLock,
-            std::this_thread::get_id() != mMainThreadId);
+
+    ConditionalLock lock(mStateLock, std::this_thread::get_id() != mMainThreadId);
+
     if (!getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE)) {
         outSupported->push_back(FrameEvent::DISPLAY_PRESENT);
     }
@@ -1040,13 +1001,8 @@
 
         outMode.resolution = ui::Size(width, height);
 
-        if (mEmulatedDisplayDensity) {
-            outMode.xDpi = mEmulatedDisplayDensity;
-            outMode.yDpi = mEmulatedDisplayDensity;
-        } else {
-            outMode.xDpi = xDpi;
-            outMode.yDpi = yDpi;
-        }
+        outMode.xDpi = xDpi;
+        outMode.yDpi = yDpi;
 
         const nsecs_t period = mode->getVsyncPeriod();
         outMode.refreshRate = Fps::fromPeriodNsecs(period).getValue();
@@ -1142,7 +1098,7 @@
     }
 
     auto future = mScheduler->schedule([=]() -> status_t {
-        const auto display = ON_MAIN_THREAD(getDisplayDeviceLocked(displayToken));
+        const auto display = FTL_FAKE_GUARD(mStateLock, getDisplayDeviceLocked(displayToken));
         if (!display) {
             ALOGE("Attempt to set allowed display modes for invalid display token %p",
                   displayToken.get());
@@ -1183,7 +1139,9 @@
         return;
     }
 
-    const auto upcomingModeInfo = MAIN_THREAD_GUARD(display->getUpcomingActiveMode());
+    const auto upcomingModeInfo =
+            FTL_FAKE_GUARD(kMainThreadContext, display->getUpcomingActiveMode());
+
     if (!upcomingModeInfo.mode) {
         // There is no pending mode change. This can happen if the active
         // display changed and the mode change happened on a different display.
@@ -1202,9 +1160,8 @@
         return;
     }
 
-    // We just created this display so we can call even if we are not on
-    // the main thread
-    MainThreadScopedGuard fakeMainThreadGuard(SF_MAIN_THREAD);
+    // We just created this display so we can call even if we are not on the main thread.
+    ftl::FakeGuard guard(kMainThreadContext);
     display->setActiveMode(upcomingModeInfo.mode->getId());
 
     const Fps refreshRate = upcomingModeInfo.mode->getFps();
@@ -1289,8 +1246,10 @@
         constraints.seamlessRequired = false;
         hal::VsyncPeriodChangeTimeline outTimeline;
 
-        const auto status = MAIN_THREAD_GUARD(
-                display->initiateModeChange(*desiredActiveMode, constraints, &outTimeline));
+        const auto status = FTL_FAKE_GUARD(kMainThreadContext,
+                                           display->initiateModeChange(*desiredActiveMode,
+                                                                       constraints, &outTimeline));
+
         if (status != NO_ERROR) {
             // initiateModeChange may fail if a hotplug event is just about
             // to be sent. We just log the error in this case.
@@ -1323,12 +1282,13 @@
 }
 
 void SurfaceFlinger::disableExpensiveRendering() {
-    auto future = mScheduler->schedule([=]() MAIN_THREAD {
-        ATRACE_CALL();
-        if (mPowerAdvisor.isUsingExpensiveRendering()) {
+    const char* const whence = __func__;
+    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
+        ATRACE_NAME(whence);
+        if (mPowerAdvisor->isUsingExpensiveRendering()) {
             for (const auto& [_, display] : mDisplays) {
                 constexpr bool kDisable = false;
-                mPowerAdvisor.setExpensiveRenderingExpected(display->getId(), kDisable);
+                mPowerAdvisor->setExpensiveRenderingExpected(display->getId(), kDisable);
             }
         }
     });
@@ -1353,17 +1313,25 @@
 }
 
 status_t SurfaceFlinger::getDisplayNativePrimaries(const sp<IBinder>& displayToken,
-                                                   ui::DisplayPrimaries &primaries) {
+                                                   ui::DisplayPrimaries& primaries) {
     if (!displayToken) {
         return BAD_VALUE;
     }
 
-    // Currently we only support this API for a single internal display.
-    if (getInternalDisplayToken() != displayToken) {
+    Mutex::Autolock lock(mStateLock);
+
+    const auto display = getDisplayDeviceLocked(displayToken);
+    if (!display) {
         return NAME_NOT_FOUND;
     }
 
-    memcpy(&primaries, &mInternalDisplayPrimaries, sizeof(ui::DisplayPrimaries));
+    const auto connectionType = display->getConnectionType();
+    if (connectionType != ui::DisplayConnectionType::Internal) {
+        return INVALID_OPERATION;
+    }
+
+    // TODO(b/229846990): For now, assume that all internal displays have the same primaries.
+    primaries = mInternalDisplayPrimaries;
     return NO_ERROR;
 }
 
@@ -1372,7 +1340,7 @@
         return BAD_VALUE;
     }
 
-    auto future = mScheduler->schedule([=]() MAIN_THREAD -> status_t {
+    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t {
         const auto display = getDisplayDeviceLocked(displayToken);
         if (!display) {
             ALOGE("Attempt to set active color mode %s (%d) for invalid display token %p",
@@ -1407,17 +1375,17 @@
 }
 
 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();
+    auto future = mScheduler->schedule(
+            [this] { return getHwComposer().hasCapability(Capability::BOOT_DISPLAY_CONFIG); });
+
+    *outSupport = future.get();
+    return NO_ERROR;
 }
 
 status_t SurfaceFlinger::setBootDisplayMode(const sp<IBinder>& displayToken,
                                             ui::DisplayModeId modeId) {
     const char* const whence = __func__;
-    auto future = mScheduler->schedule([=]() MAIN_THREAD -> status_t {
+    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t {
         const auto display = getDisplayDeviceLocked(displayToken);
         if (!display) {
             ALOGE("%s: Invalid display token %p", whence, displayToken.get());
@@ -1444,7 +1412,7 @@
 
 status_t SurfaceFlinger::clearBootDisplayMode(const sp<IBinder>& displayToken) {
     const char* const whence = __func__;
-    auto future = mScheduler->schedule([=]() MAIN_THREAD -> status_t {
+    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t {
         if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
             return getHwComposer().clearBootDisplayMode(*displayId);
         } else {
@@ -1457,7 +1425,7 @@
 
 void SurfaceFlinger::setAutoLowLatencyMode(const sp<IBinder>& displayToken, bool on) {
     const char* const whence = __func__;
-    static_cast<void>(mScheduler->schedule([=]() MAIN_THREAD {
+    static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
         if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
             getHwComposer().setAutoLowLatencyMode(*displayId, on);
         } else {
@@ -1468,7 +1436,7 @@
 
 void SurfaceFlinger::setGameContentType(const sp<IBinder>& displayToken, bool on) {
     const char* const whence = __func__;
-    static_cast<void>(mScheduler->schedule([=]() MAIN_THREAD {
+    static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
         if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
             const auto type = on ? hal::ContentType::GAME : hal::ContentType::NONE;
             getHwComposer().setContentType(*displayId, type);
@@ -1533,7 +1501,7 @@
                                                           bool enable, uint8_t componentMask,
                                                           uint64_t maxFrames) {
     const char* const whence = __func__;
-    auto future = mScheduler->schedule([=]() MAIN_THREAD -> status_t {
+    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t {
         if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
             return getHwComposer().setDisplayContentSamplingEnabled(*displayId, enable,
                                                                     componentMask, maxFrames);
@@ -1610,7 +1578,7 @@
 status_t SurfaceFlinger::getLayerDebugInfo(std::vector<LayerDebugInfo>* outLayers) {
     outLayers->clear();
     auto future = mScheduler->schedule([=] {
-        const auto display = ON_MAIN_THREAD(getDefaultDisplayDeviceLocked());
+        const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked());
         mDrawingState.traverseInZOrder([&](Layer* layer) {
             outLayers->push_back(layer->getLayerDebugInfo(display.get()));
         });
@@ -1722,7 +1690,7 @@
     }
 
     const char* const whence = __func__;
-    return ftl::chain(mScheduler->schedule([=]() MAIN_THREAD {
+    return ftl::chain(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
                if (const auto display = getDisplayDeviceLocked(displayToken)) {
                    const bool supportsDisplayBrightnessCommand =
                            getHwComposer().getComposer()->isSupported(
@@ -1736,7 +1704,9 @@
                                compositionDisplay->editState().displayBrightnessNits;
                        compositionDisplay->setDisplayBrightness(brightness.sdrWhitePointNits,
                                                                 brightness.displayBrightnessNits);
-                       MAIN_THREAD_GUARD(display->stageBrightness(brightness.displayBrightness));
+                       FTL_FAKE_GUARD(kMainThreadContext,
+                                      display->stageBrightness(brightness.displayBrightness));
+
                        if (brightness.sdrWhitePointNits / brightness.displayBrightnessNits !=
                            currentDimmingRatio) {
                            scheduleComposite(FrameHint::kNone);
@@ -1748,6 +1718,7 @@
                        return getHwComposer()
                                .setDisplayBrightness(display->getPhysicalId(),
                                                      brightness.displayBrightness,
+                                                     brightness.displayBrightnessNits,
                                                      Hwc2::Composer::DisplayBrightnessOptions{
                                                              .applyImmediately = true});
                    }
@@ -1806,6 +1777,7 @@
 }
 
 status_t SurfaceFlinger::notifyPowerBoost(int32_t boostId) {
+    using hardware::power::Boost;
     Boost powerBoost = static_cast<Boost>(boostId);
 
     if (powerBoost == Boost::INTERACTION) {
@@ -1847,7 +1819,7 @@
     if (hint == FrameHint::kActive) {
         mScheduler->resetIdleTimer();
     }
-    mPowerAdvisor.notifyDisplayUpdateImminent();
+    mPowerAdvisor->notifyDisplayUpdateImminent();
     mScheduler->scheduleFrame();
 }
 
@@ -1966,7 +1938,7 @@
     ATRACE_CALL();
 
     // On main thread to avoid race conditions with display power state.
-    static_cast<void>(mScheduler->schedule([=]() MAIN_THREAD {
+    static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
         mHWCVsyncPendingState = enabled ? hal::Vsync::ENABLE : hal::Vsync::DISABLE;
 
         if (const auto display = getDefaultDisplayDeviceLocked();
@@ -2014,10 +1986,10 @@
                                                           : stats.vsyncTime + stats.vsyncPeriod;
 }
 
-bool SurfaceFlinger::commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expectedVsyncTime) {
-    MainThreadScopedGuard mainThreadGuard(SF_MAIN_THREAD);
+bool SurfaceFlinger::commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expectedVsyncTime)
+        FTL_FAKE_GUARD(kMainThreadContext) {
     // we set this once at the beginning of commit to ensure consistency throughout the whole frame
-    mPowerHintSessionData.sessionEnabled = mPowerAdvisor.usePowerHintSession();
+    mPowerHintSessionData.sessionEnabled = mPowerAdvisor->usePowerHintSession();
     if (mPowerHintSessionData.sessionEnabled) {
         mPowerHintSessionData.commitStart = systemTime();
     }
@@ -2036,8 +2008,8 @@
     mScheduledPresentTime = expectedVsyncTime;
 
     if (mPowerHintSessionData.sessionEnabled) {
-        mPowerAdvisor.setTargetWorkDuration(mExpectedPresentTime -
-                                            mPowerHintSessionData.commitStart);
+        mPowerAdvisor->setTargetWorkDuration(mExpectedPresentTime -
+                                             mPowerHintSessionData.commitStart);
     }
     const auto vsyncIn = [&] {
         if (!ATRACE_ENABLED()) return 0.f;
@@ -2181,20 +2153,21 @@
         mLayerTracing.notify(mVisibleRegionsDirty, frameTime);
     }
 
-    MAIN_THREAD_GUARD(persistDisplayBrightness(mustComposite));
+    persistDisplayBrightness(mustComposite);
 
     return mustComposite && CC_LIKELY(mBootStage != BootStage::BOOTLOADER);
 }
 
-void SurfaceFlinger::composite(nsecs_t frameTime, int64_t vsyncId) {
+void SurfaceFlinger::composite(nsecs_t frameTime, int64_t vsyncId)
+        FTL_FAKE_GUARD(kMainThreadContext) {
     ATRACE_FORMAT("%s %" PRId64, __func__, vsyncId);
-    MainThreadScopedGuard mainThreadGuard(SF_MAIN_THREAD);
+
     if (mPowerHintSessionData.sessionEnabled) {
         mPowerHintSessionData.compositeStart = systemTime();
     }
 
     compositionengine::CompositionRefreshArgs refreshArgs;
-    const auto& displays = ON_MAIN_THREAD(mDisplays);
+    const auto& displays = FTL_FAKE_GUARD(mStateLock, mDisplays);
     refreshArgs.outputs.reserve(displays.size());
     for (const auto& [_, display] : displays) {
         refreshArgs.outputs.push_back(display->getCompositionDisplay());
@@ -2261,24 +2234,24 @@
 
     const bool prevFrameHadClientComposition = mHadClientComposition;
 
-    mHadClientComposition = std::any_of(displays.cbegin(), displays.cend(), [](const auto& pair) {
-        const auto& state = pair.second->getCompositionDisplay()->getState();
-        return state.usesClientComposition && !state.reusedClientComposition;
-    });
-    mHadDeviceComposition = std::any_of(displays.cbegin(), displays.cend(), [](const auto& pair) {
-        const auto& state = pair.second->getCompositionDisplay()->getState();
-        return state.usesDeviceComposition;
-    });
-    mReusedClientComposition =
-            std::any_of(displays.cbegin(), displays.cend(), [](const auto& pair) {
-                const auto& state = pair.second->getCompositionDisplay()->getState();
-                return state.reusedClientComposition;
-            });
-    // Only report a strategy change if we move in and out of client composition
-    if (prevFrameHadClientComposition != mHadClientComposition) {
-        mTimeStats->incrementCompositionStrategyChanges();
+    mHadClientComposition = mHadDeviceComposition = mReusedClientComposition = false;
+    TimeStats::ClientCompositionRecord clientCompositionRecord;
+    for (const auto& [_, display] : displays) {
+        const auto& state = display->getCompositionDisplay()->getState();
+        mHadClientComposition |= state.usesClientComposition && !state.reusedClientComposition;
+        mHadDeviceComposition |= state.usesDeviceComposition;
+        mReusedClientComposition |= state.reusedClientComposition;
+        clientCompositionRecord.predicted |=
+                (state.strategyPrediction != CompositionStrategyPredictionState::DISABLED);
+        clientCompositionRecord.predictionSucceeded |=
+                (state.strategyPrediction == CompositionStrategyPredictionState::SUCCESS);
     }
 
+    clientCompositionRecord.hadClientComposition = mHadClientComposition;
+    clientCompositionRecord.reused = mReusedClientComposition;
+    clientCompositionRecord.changed = prevFrameHadClientComposition != mHadClientComposition;
+    mTimeStats->pushCompositionStrategyState(clientCompositionRecord);
+
     // TODO: b/160583065 Enable skip validation when SF caches all client composition layers
     const bool usedGpuComposition = mHadClientComposition || mReusedClientComposition;
     modulateVsync(&VsyncModulator::onDisplayRefresh, usedGpuComposition);
@@ -2300,7 +2273,7 @@
     if (mPowerHintSessionData.sessionEnabled) {
         const nsecs_t flingerDuration =
                 (mPowerHintSessionData.presentEnd - mPowerHintSessionData.commitStart);
-        mPowerAdvisor.sendActualWorkDuration(flingerDuration, mPowerHintSessionData.presentEnd);
+        mPowerAdvisor->sendActualWorkDuration(flingerDuration, mPowerHintSessionData.presentEnd);
     }
 }
 
@@ -2380,7 +2353,12 @@
 }
 
 bool SurfaceFlinger::isHdrLayer(Layer* layer) const {
-    if (!isHdrDataspace(layer->getDataSpace())) {
+    // Treat all layers as non-HDR if:
+    // 1. They do not have a valid HDR dataspace. Currently we treat those as PQ or HLG. and
+    // 2. The layer is allowed to be dimmed. WindowManager may disable dimming in order to
+    // keep animations invoking SDR screenshots of HDR layers seamless. Treat such tagged
+    // layers as HDR so that DisplayManagerService does not try to change the screen brightness
+    if (!isHdrDataspace(layer->getDataSpace()) && layer->isDimmingEnabled()) {
         return false;
     }
     if (mIgnoreHdrCameraLayers) {
@@ -2432,7 +2410,7 @@
     ATRACE_CALL();
     ALOGV("postComposition");
 
-    const auto* display = ON_MAIN_THREAD(getDefaultDisplayDeviceLocked()).get();
+    const auto* display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get();
 
     getBE().mGlCompositionDoneTimeline.updateSignalTimes();
     std::shared_ptr<FenceTime> glCompositionDoneFenceTime;
@@ -2569,13 +2547,6 @@
     }
 
     mTimeStats->incrementTotalFrames();
-    if (mHadClientComposition) {
-        mTimeStats->incrementClientCompositionFrames();
-    }
-
-    if (mReusedClientComposition) {
-        mTimeStats->incrementClientCompositionReusedFrames();
-    }
 
     mTimeStats->setPresentFenceGlobal(mPreviousPresentFences[0].fenceTime);
 
@@ -2632,44 +2603,37 @@
 }
 
 FloatRect SurfaceFlinger::getMaxDisplayBounds() {
-    // Find the largest width and height among all the displays.
-    int32_t maxDisplayWidth = 0;
-    int32_t maxDisplayHeight = 0;
+    const ui::Size maxSize = [this] {
+        ftl::FakeGuard guard(mStateLock);
 
-    // 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;
-    }
+        // The LayerTraceGenerator tool runs without displays.
+        if (mDisplays.empty()) return ui::Size{5000, 5000};
 
-    for (const auto& pair : ON_MAIN_THREAD(mDisplays)) {
-        const auto& displayDevice = pair.second;
-        int32_t width = displayDevice->getWidth();
-        int32_t height = displayDevice->getHeight();
-        if (width > maxDisplayWidth) {
-            maxDisplayWidth = width;
-        }
-        if (height > maxDisplayHeight) {
-            maxDisplayHeight = height;
-        }
-    }
+        return std::accumulate(mDisplays.begin(), mDisplays.end(), ui::kEmptySize,
+                               [](ui::Size size, const auto& pair) -> ui::Size {
+                                   const auto& display = pair.second;
+                                   return {std::max(size.getWidth(), display->getWidth()),
+                                           std::max(size.getHeight(), display->getHeight())};
+                               });
+    }();
 
     // Ignore display bounds for now since they will be computed later. Use a large Rect bound
     // to ensure it's bigger than an actual display will be.
-    FloatRect maxBounds = FloatRect(-maxDisplayWidth * 10, -maxDisplayHeight * 10,
-                                    maxDisplayWidth * 10, maxDisplayHeight * 10);
-    return maxBounds;
+    const float xMax = maxSize.getWidth() * 10.f;
+    const float yMax = maxSize.getHeight() * 10.f;
+
+    return {-xMax, -yMax, xMax, yMax};
 }
 
 void SurfaceFlinger::computeLayerBounds() {
-    FloatRect maxBounds = getMaxDisplayBounds();
+    const FloatRect maxBounds = getMaxDisplayBounds();
     for (const auto& layer : mDrawingState.layersSortedByZ) {
         layer->computeBounds(maxBounds, ui::Transform(), 0.f /* shadowRadius */);
     }
 }
 
 void SurfaceFlinger::postFrame() {
-    const auto display = ON_MAIN_THREAD(getDefaultDisplayDeviceLocked());
+    const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked());
     if (display && getHwComposer().isConnected(display->getPhysicalId())) {
         uint32_t flipCount = display->getPageFlipCount();
         if (flipCount % LOG_FRAME_STATS_PERIOD == 0) {
@@ -2778,12 +2742,12 @@
         }
 
         const auto displayId = info->id;
-        const auto it = mPhysicalDisplayTokens.find(displayId);
+        const auto token = mPhysicalDisplayTokens.get(displayId);
 
         if (event.connection == hal::Connection::CONNECTED) {
             auto [supportedModes, activeMode] = loadDisplayModes(displayId);
 
-            if (it == mPhysicalDisplayTokens.end()) {
+            if (!token) {
                 ALOGV("Creating display %s", to_string(displayId).c_str());
 
                 DisplayDeviceState state;
@@ -2798,14 +2762,13 @@
 
                 sp<IBinder> token = new BBinder();
                 mCurrentState.displays.add(token, state);
-                mPhysicalDisplayTokens.emplace(displayId, std::move(token));
+                mPhysicalDisplayTokens.try_emplace(displayId, std::move(token));
                 mInterceptor->saveDisplayCreation(state);
             } else {
                 ALOGV("Recreating display %s", to_string(displayId).c_str());
 
-                const auto token = it->second;
-                auto& state = mCurrentState.displays.editValueFor(token);
-                state.sequenceId = DisplayDeviceState{}.sequenceId; // Generate new sequenceId
+                auto& state = mCurrentState.displays.editValueFor(token->get());
+                state.sequenceId = DisplayDeviceState{}.sequenceId; // Generate new sequenceId.
                 state.physical->supportedModes = std::move(supportedModes);
                 state.physical->activeMode = std::move(activeMode);
                 if (getHwComposer().updatesDeviceProductInfoOnHotplugReconnect()) {
@@ -2815,13 +2778,13 @@
         } else {
             ALOGV("Removing display %s", to_string(displayId).c_str());
 
-            const ssize_t index = mCurrentState.displays.indexOfKey(it->second);
-            if (index >= 0) {
+            if (const ssize_t index = mCurrentState.displays.indexOfKey(token->get()); index >= 0) {
                 const DisplayDeviceState& state = mCurrentState.displays.valueAt(index);
                 mInterceptor->saveDisplayDeletion(state.sequenceId);
                 mCurrentState.displays.removeItemsAt(index);
             }
-            mPhysicalDisplayTokens.erase(it);
+
+            mPhysicalDisplayTokens.erase(displayId);
         }
 
         processDisplayChangesLocked();
@@ -2869,7 +2832,7 @@
     }
 
     if (const auto id = PhysicalDisplayId::tryCast(compositionDisplay->getId())) {
-        creationArgs.isPrimary = id == getInternalDisplayIdLocked();
+        creationArgs.isPrimary = id == getPrimaryDisplayIdLocked();
 
         if (useColorManagement) {
             std::vector<ColorMode> modes = getHwComposer().getColorModes(*id);
@@ -2923,7 +2886,8 @@
                                                     RenderIntent::COLORIMETRIC,
                                                     Dataspace::UNKNOWN});
     if (!state.isVirtual()) {
-        MAIN_THREAD_GUARD(display->setActiveMode(state.physical->activeMode->getId()));
+        FTL_FAKE_GUARD(kMainThreadContext,
+                       display->setActiveMode(state.physical->activeMode->getId()));
         display->setDeviceProductInfo(state.physical->deviceProductInfo);
     }
 
@@ -2968,7 +2932,7 @@
 
     builder.setPixels(resolution);
     builder.setIsSecure(state.isSecure);
-    builder.setPowerAdvisor(&mPowerAdvisor);
+    builder.setPowerAdvisor(mPowerAdvisor.get());
     builder.setName(state.displayName);
     auto compositionDisplay = getCompositionEngine().createDisplay(builder.build());
     compositionDisplay->setLayerCachingEnabled(mLayerCachingEnabled);
@@ -3001,15 +2965,16 @@
     }
 
     LOG_FATAL_IF(!displaySurface);
-    const auto display = setupNewDisplayDeviceInternal(displayToken, std::move(compositionDisplay),
-                                                       state, displaySurface, producer);
-    mDisplays.emplace(displayToken, display);
+    auto display = setupNewDisplayDeviceInternal(displayToken, std::move(compositionDisplay), state,
+                                                 displaySurface, producer);
     if (display->isPrimary()) {
         initScheduler(display);
     }
     if (!state.isVirtual()) {
         dispatchDisplayHotplugEvent(display->getPhysicalId(), true);
     }
+
+    mDisplays.try_emplace(displayToken, std::move(display));
 }
 
 void SurfaceFlinger::processDisplayRemoved(const wp<IBinder>& displayToken) {
@@ -3293,12 +3258,15 @@
         return;
     }
 
-    for (const auto& [_, display] : ON_MAIN_THREAD(mDisplays)) {
+    for (const auto& [_, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
         if (const auto brightness = display->getStagedBrightness(); brightness) {
             if (!needsComposite) {
                 const status_t error =
                         getHwComposer()
                                 .setDisplayBrightness(display->getPhysicalId(), *brightness,
+                                                      display->getCompositionDisplay()
+                                                              ->getState()
+                                                              .displayBrightnessNits,
                                                       Hwc2::Composer::DisplayBrightnessOptions{
                                                               .applyImmediately = true})
                                 .get();
@@ -3316,7 +3284,7 @@
                                       std::vector<DisplayInfo>& outDisplayInfos) {
     ftl::SmallMap<ui::LayerStack, DisplayDevice::InputInfo, 4> displayInputInfos;
 
-    for (const auto& [_, display] : ON_MAIN_THREAD(mDisplays)) {
+    for (const auto& [_, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
         const auto layerStack = display->getLayerStack();
         const auto info = display->getInputInfo();
 
@@ -3361,7 +3329,7 @@
 
 void SurfaceFlinger::updateCursorAsync() {
     compositionengine::CompositionRefreshArgs refreshArgs;
-    for (const auto& [_, display] : ON_MAIN_THREAD(mDisplays)) {
+    for (const auto& [_, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
         if (HalDisplayId::tryCast(display->getId())) {
             refreshArgs.outputs.push_back(display->getCompositionDisplay());
         }
@@ -3546,7 +3514,7 @@
 }
 
 void SurfaceFlinger::invalidateLayerStack(const sp<const Layer>& layer, const Region& dirty) {
-    for (const auto& [token, displayDevice] : ON_MAIN_THREAD(mDisplays)) {
+    for (const auto& [token, displayDevice] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
         auto display = displayDevice->getCompositionDisplay();
         if (display->includesLayer(layer->getOutputFilter())) {
             display->editState().dirtyRegion.orSelf(dirty);
@@ -3647,6 +3615,23 @@
     if (mNumLayers >= ISurfaceComposer::MAX_LAYERS) {
         ALOGE("AddClientLayer failed, mNumLayers (%zu) >= MAX_LAYERS (%zu)", mNumLayers.load(),
               ISurfaceComposer::MAX_LAYERS);
+        static_cast<void>(mScheduler->schedule([=] {
+            ALOGE("Dumping random sampling of on-screen layers: ");
+            mDrawingState.traverse([&](Layer *layer) {
+                // Aim to dump about 200 layers to avoid totally trashing
+                // logcat. On the other hand, if there really are 4096 layers
+                // something has gone totally wrong its probably the most
+                // useful information in logcat.
+                if (rand() % 20 == 13) {
+                    ALOGE("Layer: %s", layer->getName().c_str());
+                }
+            });
+            for (Layer* offscreenLayer : mOffscreenLayers) {
+                if (rand() % 20 == 13) {
+                    ALOGE("Offscreen-layer: %s", offscreenLayer->getName().c_str());
+                }
+            }
+        }));
         return NO_MEMORY;
     }
 
@@ -3677,11 +3662,11 @@
 }
 
 void SurfaceFlinger::setTransactionFlags(uint32_t mask, TransactionSchedule schedule,
-                                         const sp<IBinder>& applyToken) {
+                                         const sp<IBinder>& applyToken, FrameHint frameHint) {
     modulateVsync(&VsyncModulator::setTransactionSchedule, schedule, applyToken);
 
     if (const bool scheduled = mTransactionFlags.fetch_or(mask) & mask; !scheduled) {
-        scheduleCommit(FrameHint::kActive);
+        scheduleCommit(frameHint);
     }
 }
 
@@ -4053,7 +4038,7 @@
 }
 
 void SurfaceFlinger::queueTransaction(TransactionState& state) {
-    Mutex::Autolock _l(mQueueLock);
+    Mutex::Autolock lock(mQueueLock);
 
     // Generate a CountDownLatch pending state if this is a synchronous transaction.
     if ((state.flags & eSynchronous) || state.inputWindowCommands.syncInputWindows) {
@@ -4072,7 +4057,9 @@
         return TransactionSchedule::Late;
     }(state.flags);
 
-    setTransactionFlags(eTransactionFlushNeeded, schedule, state.applyToken);
+    const auto frameHint = state.isFrameActive() ? FrameHint::kActive : FrameHint::kNone;
+
+    setTransactionFlags(eTransactionFlushNeeded, schedule, state.applyToken, frameHint);
 }
 
 void SurfaceFlinger::waitForSynchronousTransaction(
@@ -4534,6 +4521,9 @@
     if (what & layer_state_t::eAutoRefreshChanged) {
         layer->setAutoRefresh(s.autoRefresh);
     }
+    if (what & layer_state_t::eDimmingEnabledChanged) {
+        if (layer->setDimmingEnabled(s.dimmingEnabled)) flags |= eTraversalNeeded;
+    }
     if (what & layer_state_t::eTrustedOverlayChanged) {
         if (layer->setTrustedOverlay(s.isTrustedOverlay)) {
             flags |= eTraversalNeeded;
@@ -4832,7 +4822,8 @@
 
 void SurfaceFlinger::initializeDisplays() {
     // Async since we may be called from the main thread.
-    static_cast<void>(mScheduler->schedule([this]() MAIN_THREAD { onInitializeDisplays(); }));
+    static_cast<void>(
+            mScheduler->schedule([this]() FTL_FAKE_GUARD(mStateLock) { onInitializeDisplays(); }));
 }
 
 void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal::PowerMode mode) {
@@ -4932,7 +4923,7 @@
 }
 
 void SurfaceFlinger::setPowerMode(const sp<IBinder>& displayToken, int mode) {
-    auto future = mScheduler->schedule([=]() MAIN_THREAD {
+    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
         const auto display = getDisplayDeviceLocked(displayToken);
         if (!display) {
             ALOGE("Attempt to set power mode %d for invalid display token %p", mode,
@@ -4960,7 +4951,9 @@
                       pid, uid);
     } else {
         static const std::unordered_map<std::string, Dumper> dumpers = {
+                {"--comp-displays"s, dumper(&SurfaceFlinger::dumpCompositionDisplays)},
                 {"--display-id"s, dumper(&SurfaceFlinger::dumpDisplayIdentificationData)},
+                {"--displays"s, dumper(&SurfaceFlinger::dumpDisplays)},
                 {"--dispsync"s, dumper([this](std::string& s) { mScheduler->dumpVsync(s); })},
                 {"--edid"s, argsDumper(&SurfaceFlinger::dumpRawDisplayIdentificationData)},
                 {"--frame-events"s, dumper(&SurfaceFlinger::dumpFrameEventsLocked)},
@@ -4979,7 +4972,7 @@
 
         bool dumpLayers = true;
         {
-            TimedLock lock(mStateLock, s2ns(1), __FUNCTION__);
+            TimedLock lock(mStateLock, s2ns(1), __func__);
             if (!lock.locked()) {
                 StringAppendF(&result, "Dumping without lock after timeout: %s (%d)\n",
                               strerror(-lock.status), lock.status);
@@ -5067,8 +5060,6 @@
     mFrameTimeline->parseArgs(args, result);
 }
 
-// This should only be called from the main thread.  Otherwise it would need
-// the lock and should use mCurrentState rather than mDrawingState.
 void SurfaceFlinger::logFrameStats() {
     mDrawingState.traverse([&](Layer* layer) {
         layer->logFrameStats();
@@ -5139,6 +5130,20 @@
         [&] (Layer* layer) { layer->dumpFrameEvents(result); });
 }
 
+void SurfaceFlinger::dumpCompositionDisplays(std::string& result) const {
+    for (const auto& [token, display] : mDisplays) {
+        display->getCompositionDisplay()->dump(result);
+        result += '\n';
+    }
+}
+
+void SurfaceFlinger::dumpDisplays(std::string& result) const {
+    for (const auto& [token, display] : mDisplays) {
+        display->dump(result);
+        result += '\n';
+    }
+}
+
 void SurfaceFlinger::dumpDisplayIdentificationData(std::string& result) const {
     for (const auto& [token, display] : mDisplays) {
         const auto displayId = PhysicalDisplayId::tryCast(display->getId());
@@ -5226,7 +5231,7 @@
 }
 
 void SurfaceFlinger::dumpDisplayProto(LayersTraceProto& layersTraceProto) const {
-    for (const auto& [_, display] : ON_MAIN_THREAD(mDisplays)) {
+    for (const auto& [_, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
         DisplayProto* displayProto = layersTraceProto.add_displays();
         displayProto->set_id(display->getId().value);
         displayProto->set_name(display->getDisplayName());
@@ -5347,21 +5352,12 @@
         });
     }
 
-    /*
-     * Dump Display state
-     */
-
     colorizer.bold(result);
     StringAppendF(&result, "Displays (%zu entries)\n", mDisplays.size());
     colorizer.reset(result);
-    for (const auto& [token, display] : mDisplays) {
-        display->dump(result);
-    }
-    result.append("\n");
-
-    /*
-     * Dump CompositionEngine state
-     */
+    dumpDisplays(result);
+    dumpCompositionDisplays(result);
+    result.push_back('\n');
 
     mCompositionEngine->dump(result);
 
@@ -5513,30 +5509,21 @@
         // access to SF.
         case BOOT_FINISHED:
         case CLEAR_ANIMATION_FRAME_STATS:
-        case CREATE_DISPLAY:
-        case DESTROY_DISPLAY:
         case GET_ANIMATION_FRAME_STATS:
         case OVERRIDE_HDR_TYPES:
         case GET_HDR_CAPABILITIES:
         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:
-        case SET_GAME_CONTENT_TYPE:
-        case SET_POWER_MODE:
         case GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES:
         case SET_DISPLAY_CONTENT_SAMPLING_ENABLED:
         case GET_DISPLAYED_CONTENT_SAMPLE:
         case ADD_TUNNEL_MODE_ENABLED_LISTENER:
         case REMOVE_TUNNEL_MODE_ENABLED_LISTENER:
-        case NOTIFY_POWER_BOOST:
         case SET_GLOBAL_SHADOW_SETTINGS:
-        case GET_PRIMARY_PHYSICAL_DISPLAY_ID:
         case ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN: {
             // OVERRIDE_HDR_TYPES is used by CTS tests, which acquire the necessary
             // permission dynamically. Don't use the permission cache for this check.
@@ -5567,15 +5554,11 @@
         case AUTHENTICATE_SURFACE:
         case GET_ACTIVE_COLOR_MODE:
         case GET_ACTIVE_DISPLAY_MODE:
-        case GET_PHYSICAL_DISPLAY_IDS:
-        case GET_PHYSICAL_DISPLAY_TOKEN:
         case GET_DISPLAY_COLOR_MODES:
         case GET_DISPLAY_NATIVE_PRIMARIES:
         case GET_STATIC_DISPLAY_INFO:
         case GET_DYNAMIC_DISPLAY_INFO:
         case GET_DISPLAY_MODES:
-        case GET_DISPLAY_STATE:
-        case GET_DISPLAY_STATS:
         case GET_SUPPORTED_FRAME_TIMESTAMPS:
         // Calling setTransactionState is safe, because you need to have been
         // granted a reference to Client* and Handle* to do anything with it.
@@ -5584,11 +5567,9 @@
         case GET_COLOR_MANAGEMENT:
         case GET_COMPOSITION_PREFERENCE:
         case GET_PROTECTED_CONTENT_SUPPORT:
-        case IS_WIDE_COLOR_DISPLAY:
         // setFrameRate() is deliberately available for apps to call without any
         // special permissions.
         case SET_FRAME_RATE:
-        case GET_DISPLAY_BRIGHTNESS_SUPPORT:
         case GET_DISPLAY_DECORATION_SUPPORT:
         case SET_FRAME_TIMELINE_INFO:
         case GET_GPU_CONTEXT_PRIORITY:
@@ -5596,19 +5577,6 @@
             // This is not sensitive information, so should not require permission control.
             return OK;
         }
-        case SET_DISPLAY_BRIGHTNESS:
-        case ADD_HDR_LAYER_INFO_LISTENER:
-        case REMOVE_HDR_LAYER_INFO_LISTENER: {
-            IPCThreadState* ipc = IPCThreadState::self();
-            const int pid = ipc->getCallingPid();
-            const int uid = ipc->getCallingUid();
-            if ((uid != AID_GRAPHICS) &&
-                !PermissionCache::checkPermission(sControlDisplayBrightness, pid, uid)) {
-                ALOGE("Permission Denial: can't control brightness pid=%d, uid=%d", pid, uid);
-                return PERMISSION_DENIED;
-            }
-            return OK;
-        }
         case ADD_FPS_LISTENER:
         case REMOVE_FPS_LISTENER:
         case ADD_REGION_SAMPLING_LISTENER:
@@ -5654,10 +5622,28 @@
             }
             return PERMISSION_DENIED;
         }
+        case CREATE_DISPLAY:
+        case DESTROY_DISPLAY:
+        case GET_PRIMARY_PHYSICAL_DISPLAY_ID:
+        case GET_PHYSICAL_DISPLAY_IDS:
+        case GET_PHYSICAL_DISPLAY_TOKEN:
+        case SET_POWER_MODE:
+        case GET_DISPLAY_STATE:
+        case GET_DISPLAY_STATS:
+        case CLEAR_BOOT_DISPLAY_MODE:
+        case GET_BOOT_DISPLAY_MODE_SUPPORT:
+        case SET_AUTO_LOW_LATENCY_MODE:
+        case SET_GAME_CONTENT_TYPE:
         case CAPTURE_LAYERS:
         case CAPTURE_DISPLAY:
         case CAPTURE_DISPLAY_BY_ID:
-            LOG_FATAL("Deprecated opcode: %d", code);
+        case IS_WIDE_COLOR_DISPLAY:
+        case GET_DISPLAY_BRIGHTNESS_SUPPORT:
+        case SET_DISPLAY_BRIGHTNESS:
+        case ADD_HDR_LAYER_INFO_LISTENER:
+        case REMOVE_HDR_LAYER_INFO_LISTENER:
+        case NOTIFY_POWER_BOOST:
+            LOG_FATAL("Deprecated opcode: %d, migrated to AIDL", code);
             return PERMISSION_DENIED;
     }
 
@@ -5867,7 +5853,7 @@
                         int64_t startingTime =
                                 (fixedStartingTime) ? fixedStartingTime : systemTime();
                         mScheduler
-                                ->schedule([&]() MAIN_THREAD {
+                                ->schedule([&]() FTL_FAKE_GUARD(mStateLock) {
                                     mLayerTracing.notify("start", startingTime);
                                 })
                                 .wait();
@@ -5980,10 +5966,12 @@
                     switch (n = data.readInt32()) {
                         case 0:
                         case 1:
-                            ON_MAIN_THREAD(enableRefreshRateOverlay(static_cast<bool>(n)));
+                            FTL_FAKE_GUARD(mStateLock,
+                                           enableRefreshRateOverlay(static_cast<bool>(n)));
                             break;
                         default: {
-                            reply->writeBool(ON_MAIN_THREAD(isRefreshRateOverlayEnabled()));
+                            reply->writeBool(
+                                    FTL_FAKE_GUARD(mStateLock, isRefreshRateOverlayEnabled()));
                         }
                     }
                 });
@@ -6021,7 +6009,7 @@
                     return mScheduler
                             ->schedule([this] {
                                 const auto display =
-                                        ON_MAIN_THREAD(getDefaultDisplayDeviceLocked());
+                                        FTL_FAKE_GUARD(mStateLock, 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
@@ -6043,7 +6031,7 @@
                     return mScheduler
                             ->schedule([this] {
                                 const auto display =
-                                        ON_MAIN_THREAD(getDefaultDisplayDeviceLocked());
+                                        FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked());
                                 constexpr bool kOverridePolicy = true;
                                 return setDesiredDisplayModeSpecsInternal(display, {},
                                                                           kOverridePolicy);
@@ -6159,7 +6147,7 @@
     // Update the overlay on the main thread to avoid race conditions with
     // mRefreshRateConfigs->getActiveMode()
     static_cast<void>(mScheduler->schedule([=] {
-        const auto display = ON_MAIN_THREAD(getDefaultDisplayDeviceLocked());
+        const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked());
         if (!display) {
             ALOGW("%s: default display is null", __func__);
             return;
@@ -6720,6 +6708,7 @@
     captureResults.buffer = buffer->getBuffer();
     auto dataspace = renderArea.getReqDataSpace();
     auto parent = renderArea.getParentLayer();
+    auto renderIntent = RenderIntent::TONE_MAP_COLORIMETRIC;
     if ((dataspace == ui::Dataspace::UNKNOWN) && (parent != nullptr)) {
         Mutex::Autolock lock(mStateLock);
         auto display = findDisplay([layerStack = parent->getLayerStack()](const auto& display) {
@@ -6732,6 +6721,7 @@
 
         const ui::ColorMode colorMode = display->getCompositionDisplay()->getState().colorMode;
         dataspace = pickDataspaceFromColorMode(colorMode);
+        renderIntent = display->getCompositionDisplay()->getState().renderIntent;
     }
     captureResults.capturedDataspace = dataspace;
 
@@ -6753,6 +6743,8 @@
 
     clientCompositionDisplay.outputDataspace = dataspace;
     clientCompositionDisplay.maxLuminance = DisplayDevice::sDefaultMaxLumiance;
+    clientCompositionDisplay.renderIntent =
+            static_cast<aidl::android::hardware::graphics::composer3::RenderIntent>(renderIntent);
 
     const float colorSaturation = grayscale ? 0 : 1;
     clientCompositionDisplay.colorTransform = calculateColorMatrix(colorSaturation);
@@ -6957,7 +6949,7 @@
     }
 
     auto future = mScheduler->schedule([=]() -> status_t {
-        const auto display = ON_MAIN_THREAD(getDisplayDeviceLocked(displayToken));
+        const auto display = FTL_FAKE_GUARD(mStateLock, getDisplayDeviceLocked(displayToken));
         if (!display) {
             ALOGE("Attempt to set desired display modes for invalid display token %p",
                   displayToken.get());
@@ -7202,7 +7194,7 @@
     if (const auto frameRateOverride = mScheduler->getFrameRateOverride(uid)) {
         refreshRate = *frameRateOverride;
     } else if (!getHwComposer().isHeadless()) {
-        if (const auto display = ON_MAIN_THREAD(getDefaultDisplayDeviceLocked())) {
+        if (const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked())) {
             refreshRate = display->refreshRateConfigs().getActiveMode()->getFps();
         }
     }
@@ -7216,15 +7208,6 @@
     return calculateMaxAcquiredBufferCount(refreshRate, presentLatency);
 }
 
-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) {
-            visitor(state.state);
-        }
-    }
-}
-
 void SurfaceFlinger::handleLayerCreatedLocked(const LayerCreatedState& state) {
     sp<Layer> layer = state.layer.promote();
     if (!layer) {
@@ -7360,6 +7343,123 @@
 }
 
 // gui::ISurfaceComposer
+
+binder::Status SurfaceComposerAIDL::createDisplay(const std::string& displayName, bool secure,
+                                                  sp<IBinder>* outDisplay) {
+    status_t status = checkAccessPermission();
+    if (status == OK) {
+        String8 displayName8 = String8::format("%s", displayName.c_str());
+        *outDisplay = mFlinger->createDisplay(displayName8, secure);
+        return binder::Status::ok();
+    }
+    return binder::Status::fromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::destroyDisplay(const sp<IBinder>& display) {
+    status_t status = checkAccessPermission();
+    if (status == OK) {
+        mFlinger->destroyDisplay(display);
+        return binder::Status::ok();
+    }
+    return binder::Status::fromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::getPhysicalDisplayIds(std::vector<int64_t>* outDisplayIds) {
+    std::vector<PhysicalDisplayId> physicalDisplayIds = mFlinger->getPhysicalDisplayIds();
+    std::vector<int64_t> displayIds;
+    displayIds.reserve(physicalDisplayIds.size());
+    for (auto item : physicalDisplayIds) {
+        displayIds.push_back(static_cast<int64_t>(item.value));
+    }
+    *outDisplayIds = displayIds;
+    return binder::Status::ok();
+}
+
+binder::Status SurfaceComposerAIDL::getPrimaryPhysicalDisplayId(int64_t* outDisplayId) {
+    status_t status = checkAccessPermission();
+    if (status != OK) {
+        return binder::Status::fromStatusT(status);
+    }
+
+    PhysicalDisplayId id;
+    status = mFlinger->getPrimaryPhysicalDisplayId(&id);
+    if (status == NO_ERROR) {
+        *outDisplayId = id.value;
+    }
+    return binder::Status::fromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::getPhysicalDisplayToken(int64_t displayId,
+                                                            sp<IBinder>* outDisplay) {
+    const auto id = DisplayId::fromValue<PhysicalDisplayId>(static_cast<uint64_t>(displayId));
+    *outDisplay = mFlinger->getPhysicalDisplayToken(*id);
+    return binder::Status::ok();
+}
+
+binder::Status SurfaceComposerAIDL::setPowerMode(const sp<IBinder>& display, int mode) {
+    status_t status = checkAccessPermission();
+    if (status != OK) return binder::Status::fromStatusT(status);
+
+    mFlinger->setPowerMode(display, mode);
+    return binder::Status::ok();
+}
+
+binder::Status SurfaceComposerAIDL::getDisplayStats(const sp<IBinder>& display,
+                                                    gui::DisplayStatInfo* outStatInfo) {
+    DisplayStatInfo statInfo;
+    status_t status = mFlinger->getDisplayStats(display, &statInfo);
+    if (status == NO_ERROR) {
+        outStatInfo->vsyncTime = static_cast<long>(statInfo.vsyncTime);
+        outStatInfo->vsyncPeriod = static_cast<long>(statInfo.vsyncPeriod);
+    }
+    return binder::Status::fromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::getDisplayState(const sp<IBinder>& display,
+                                                    gui::DisplayState* outState) {
+    ui::DisplayState state;
+    status_t status = mFlinger->getDisplayState(display, &state);
+    if (status == NO_ERROR) {
+        outState->layerStack = state.layerStack.id;
+        outState->orientation = static_cast<gui::Rotation>(state.orientation);
+        outState->layerStackSpaceRect.width = state.layerStackSpaceRect.width;
+        outState->layerStackSpaceRect.height = state.layerStackSpaceRect.height;
+    }
+    return binder::Status::fromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::clearBootDisplayMode(const sp<IBinder>& display) {
+    status_t status = checkAccessPermission();
+    if (status != OK) return binder::Status::fromStatusT(status);
+
+    status = mFlinger->clearBootDisplayMode(display);
+    return binder::Status::fromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::getBootDisplayModeSupport(bool* outMode) {
+    status_t status = checkAccessPermission();
+    if (status != OK) return binder::Status::fromStatusT(status);
+
+    status = mFlinger->getBootDisplayModeSupport(outMode);
+    return binder::Status::fromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::setAutoLowLatencyMode(const sp<IBinder>& display, bool on) {
+    status_t status = checkAccessPermission();
+    if (status != OK) return binder::Status::fromStatusT(status);
+
+    mFlinger->setAutoLowLatencyMode(display, on);
+    return binder::Status::ok();
+}
+
+binder::Status SurfaceComposerAIDL::setGameContentType(const sp<IBinder>& display, bool on) {
+    status_t status = checkAccessPermission();
+    if (status != OK) return binder::Status::fromStatusT(status);
+
+    mFlinger->setGameContentType(display, on);
+    return binder::Status::ok();
+}
+
 binder::Status SurfaceComposerAIDL::captureDisplay(
         const DisplayCaptureArgs& args, const sp<IScreenCaptureListener>& captureListener) {
     status_t status = mFlinger->captureDisplay(args, captureListener);
@@ -7386,6 +7486,75 @@
     return binder::Status::fromStatusT(status);
 }
 
+binder::Status SurfaceComposerAIDL::isWideColorDisplay(const sp<IBinder>& token,
+                                                       bool* outIsWideColorDisplay) {
+    status_t status = mFlinger->isWideColorDisplay(token, outIsWideColorDisplay);
+    return binder::Status::fromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::getDisplayBrightnessSupport(const sp<IBinder>& displayToken,
+                                                                bool* outSupport) {
+    status_t status = mFlinger->getDisplayBrightnessSupport(displayToken, outSupport);
+    return binder::Status::fromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::setDisplayBrightness(const sp<IBinder>& displayToken,
+                                                         const gui::DisplayBrightness& brightness) {
+    status_t status = checkControlDisplayBrightnessPermission();
+    if (status != OK) return binder::Status::fromStatusT(status);
+
+    status = mFlinger->setDisplayBrightness(displayToken, brightness);
+    return binder::Status::fromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::addHdrLayerInfoListener(
+        const sp<IBinder>& displayToken, const sp<gui::IHdrLayerInfoListener>& listener) {
+    status_t status = checkControlDisplayBrightnessPermission();
+    if (status != OK) return binder::Status::fromStatusT(status);
+
+    status = mFlinger->addHdrLayerInfoListener(displayToken, listener);
+    return binder::Status::fromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::removeHdrLayerInfoListener(
+        const sp<IBinder>& displayToken, const sp<gui::IHdrLayerInfoListener>& listener) {
+    status_t status = checkControlDisplayBrightnessPermission();
+    if (status != OK) return binder::Status::fromStatusT(status);
+
+    status = mFlinger->removeHdrLayerInfoListener(displayToken, listener);
+    return binder::Status::fromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::notifyPowerBoost(int boostId) {
+    status_t status = checkAccessPermission();
+    if (status != OK) return binder::Status::fromStatusT(status);
+
+    status = mFlinger->notifyPowerBoost(boostId);
+    return binder::Status::fromStatusT(status);
+}
+
+status_t SurfaceComposerAIDL::checkAccessPermission(bool usePermissionCache) {
+    if (!mFlinger->callingThreadHasUnscopedSurfaceFlingerAccess(usePermissionCache)) {
+        IPCThreadState* ipc = IPCThreadState::self();
+        ALOGE("Permission Denial: can't access SurfaceFlinger pid=%d, uid=%d", ipc->getCallingPid(),
+              ipc->getCallingUid());
+        return PERMISSION_DENIED;
+    }
+    return OK;
+}
+
+status_t SurfaceComposerAIDL::checkControlDisplayBrightnessPermission() {
+    IPCThreadState* ipc = IPCThreadState::self();
+    const int pid = ipc->getCallingPid();
+    const int uid = ipc->getCallingUid();
+    if ((uid != AID_GRAPHICS) &&
+        !PermissionCache::checkPermission(sControlDisplayBrightness, pid, uid)) {
+        ALOGE("Permission Denial: can't control brightness pid=%d, uid=%d", pid, uid);
+        return PERMISSION_DENIED;
+    }
+    return OK;
+}
+
 } // namespace android
 
 #if defined(__gl_h_)
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index f2636b4..74e0407 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -7,7 +7,6 @@
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
@@ -24,8 +23,11 @@
 
 #include <android-base/thread_annotations.h>
 #include <android/gui/BnSurfaceComposer.h>
+#include <android/gui/DisplayStatInfo.h>
+#include <android/gui/DisplayState.h>
 #include <cutils/atomic.h>
 #include <cutils/compiler.h>
+#include <ftl/small_map.h>
 #include <gui/BufferQueue.h>
 #include <gui/FrameTimestamps.h>
 #include <gui/ISurfaceComposer.h>
@@ -64,6 +66,7 @@
 #include "Scheduler/Scheduler.h"
 #include "Scheduler/VsyncModulator.h"
 #include "SurfaceFlingerFactory.h"
+#include "ThreadContext.h"
 #include "TracedOrdinal.h"
 #include "Tracing/LayerTracing.h"
 #include "Tracing/TransactionTracing.h"
@@ -183,11 +186,6 @@
     std::atomic<nsecs_t> mLastSwapTime = 0;
 };
 
-struct SCOPED_CAPABILITY UnnecessaryLock {
-    explicit UnnecessaryLock(Mutex& mutex) ACQUIRE(mutex) {}
-    ~UnnecessaryLock() RELEASE() {}
-};
-
 class SurfaceFlinger : public BnSurfaceComposer,
                        public PriorityDumper,
                        private IBinder::DeathRecipient,
@@ -346,6 +344,11 @@
     void disableExpensiveRendering();
     FloatRect getMaxDisplayBounds();
 
+    // If set, composition engine tries to predict the composition strategy provided by HWC
+    // based on the previous frame. If the strategy can be predicted, gpu composition will
+    // run parallel to the hwc validateDisplay call and re-run if the predition is incorrect.
+    bool mPredictCompositionStrategy = false;
+
 protected:
     // We're reference counted, never destroy SurfaceFlinger directly
     virtual ~SurfaceFlinger();
@@ -524,15 +527,15 @@
 
     // Implements ISurfaceComposer
     sp<ISurfaceComposerClient> createConnection() override;
-    sp<IBinder> createDisplay(const String8& displayName, bool secure) override;
-    void destroyDisplay(const sp<IBinder>& displayToken) override;
-    std::vector<PhysicalDisplayId> getPhysicalDisplayIds() const override EXCLUDES(mStateLock) {
+    sp<IBinder> createDisplay(const String8& displayName, bool secure);
+    void destroyDisplay(const sp<IBinder>& displayToken);
+    std::vector<PhysicalDisplayId> getPhysicalDisplayIds() const EXCLUDES(mStateLock) {
         Mutex::Autolock lock(mStateLock);
         return getPhysicalDisplayIdsLocked();
     }
-    status_t getPrimaryPhysicalDisplayId(PhysicalDisplayId*) const override EXCLUDES(mStateLock);
+    status_t getPrimaryPhysicalDisplayId(PhysicalDisplayId*) const EXCLUDES(mStateLock);
 
-    sp<IBinder> getPhysicalDisplayToken(PhysicalDisplayId displayId) const override;
+    sp<IBinder> getPhysicalDisplayToken(PhysicalDisplayId displayId) const;
     status_t setTransactionState(const FrameTimelineInfo& frameTimelineInfo,
                                  const Vector<ComposerState>& state,
                                  const Vector<DisplayState>& displays, uint32_t flags,
@@ -554,9 +557,9 @@
     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 getDisplayStats(const sp<IBinder>& displayToken, DisplayStatInfo* stats);
     status_t getDisplayState(const sp<IBinder>& displayToken, ui::DisplayState*)
-            EXCLUDES(mStateLock) override;
+            EXCLUDES(mStateLock);
     status_t getStaticDisplayInfo(const sp<IBinder>& displayToken, ui::StaticDisplayInfo*)
             EXCLUDES(mStateLock) override;
     status_t getDynamicDisplayInfo(const sp<IBinder>& displayToken, ui::DynamicDisplayInfo*)
@@ -564,12 +567,12 @@
     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 getBootDisplayModeSupport(bool* outSupport) const;
     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;
+    status_t clearBootDisplayMode(const sp<IBinder>& displayToken);
+    void setAutoLowLatencyMode(const sp<IBinder>& displayToken, bool on);
+    void setGameContentType(const sp<IBinder>& displayToken, bool on);
+    void setPowerMode(const sp<IBinder>& displayToken, int mode);
     status_t clearAnimationFrameStats() override;
     status_t getAnimationFrameStats(FrameStats* outStats) const override;
     status_t overrideHdrTypes(const sp<IBinder>& displayToken,
@@ -592,8 +595,7 @@
                                        uint64_t timestamp,
                                        DisplayedFrameStats* outStats) const override;
     status_t getProtectedContentSupport(bool* outSupported) const override;
-    status_t isWideColorDisplay(const sp<IBinder>& displayToken,
-                                bool* outIsWideColorDisplay) const override;
+    status_t isWideColorDisplay(const sp<IBinder>& displayToken, bool* outIsWideColorDisplay) const;
     status_t addRegionSamplingListener(const Rect& samplingArea, const sp<IBinder>& stopLayerHandle,
                                        const sp<IRegionSamplingListener>& listener) override;
     status_t removeRegionSamplingListener(const sp<IRegionSamplingListener>& listener) override;
@@ -615,15 +617,14 @@
                                         float* outPrimaryRefreshRateMax,
                                         float* outAppRequestRefreshRateMin,
                                         float* outAppRequestRefreshRateMax) override;
-    status_t getDisplayBrightnessSupport(const sp<IBinder>& displayToken,
-                                         bool* outSupport) const override;
+    status_t getDisplayBrightnessSupport(const sp<IBinder>& displayToken, bool* outSupport) const;
     status_t setDisplayBrightness(const sp<IBinder>& displayToken,
-                                  const gui::DisplayBrightness& brightness) override;
+                                  const gui::DisplayBrightness& brightness);
     status_t addHdrLayerInfoListener(const sp<IBinder>& displayToken,
-                                     const sp<gui::IHdrLayerInfoListener>& listener) override;
+                                     const sp<gui::IHdrLayerInfoListener>& listener);
     status_t removeHdrLayerInfoListener(const sp<IBinder>& displayToken,
-                                        const sp<gui::IHdrLayerInfoListener>& listener) override;
-    status_t notifyPowerBoost(int32_t boostId) override;
+                                        const sp<gui::IHdrLayerInfoListener>& listener);
+    status_t notifyPowerBoost(int32_t boostId);
     status_t setGlobalShadowSettings(const half4& ambientColor, const half4& spotColor,
                                      float lightPosY, float lightPosZ, float lightRadius) override;
     status_t getDisplayDecorationSupport(
@@ -740,7 +741,7 @@
     void updateLayerGeometry();
 
     void updateInputFlinger();
-    void persistDisplayBrightness(bool needsComposite) REQUIRES(SF_MAIN_THREAD);
+    void persistDisplayBrightness(bool needsComposite) REQUIRES(kMainThreadContext);
     void buildWindowInfos(std::vector<gui::WindowInfo>& outWindowInfos,
                           std::vector<gui::DisplayInfo>& outDisplayInfos);
     void commitInputWindowCommands() REQUIRES(mStateLock);
@@ -788,7 +789,8 @@
 
     // Sets the masked bits, and schedules a commit if needed.
     void setTransactionFlags(uint32_t mask, TransactionSchedule = TransactionSchedule::Late,
-                             const sp<IBinder>& applyToken = nullptr);
+                             const sp<IBinder>& applyToken = nullptr,
+                             FrameHint = FrameHint::kActive);
 
     // Clears and returns the masked bits.
     uint32_t clearTransactionFlags(uint32_t mask);
@@ -891,8 +893,8 @@
     }
 
     sp<DisplayDevice> getDisplayDeviceLocked(const wp<IBinder>& displayToken) REQUIRES(mStateLock) {
-        const auto it = mDisplays.find(displayToken);
-        return it == mDisplays.end() ? nullptr : it->second;
+        const sp<DisplayDevice> nullDisplay;
+        return mDisplays.get(displayToken).value_or(std::cref(nullDisplay));
     }
 
     sp<const DisplayDevice> getDisplayDeviceLocked(PhysicalDisplayId id) const
@@ -907,6 +909,13 @@
         return nullptr;
     }
 
+    sp<const DisplayDevice> getDisplayDeviceLocked(DisplayId id) const REQUIRES(mStateLock) {
+        // TODO(b/182939859): Replace tokens with IDs for display lookup.
+        return findDisplay([id](const auto& display) { return display.getId() == id; });
+    }
+
+    // Returns the primary display or (for foldables) the active display, assuming that the inner
+    // and outer displays have mutually exclusive power states.
     sp<const DisplayDevice> getDefaultDisplayDeviceLocked() const REQUIRES(mStateLock) {
         return const_cast<SurfaceFlinger*>(this)->getDefaultDisplayDeviceLocked();
     }
@@ -915,12 +924,9 @@
         if (const auto display = getDisplayDeviceLocked(mActiveDisplayToken)) {
             return display;
         }
-        // The active display is outdated, fall back to the internal display
+        // The active display is outdated, so fall back to the primary display.
         mActiveDisplayToken.clear();
-        if (const auto token = getInternalDisplayTokenLocked()) {
-            return getDisplayDeviceLocked(token);
-        }
-        return nullptr;
+        return getDisplayDeviceLocked(getPrimaryDisplayTokenLocked());
     }
 
     sp<const DisplayDevice> getDefaultDisplayDevice() const EXCLUDES(mStateLock) {
@@ -937,11 +943,6 @@
         return it == mDisplays.end() ? nullptr : it->second;
     }
 
-    sp<const DisplayDevice> getDisplayDeviceLocked(DisplayId id) const REQUIRES(mStateLock) {
-        // TODO(b/182939859): Replace tokens with IDs for display lookup.
-        return findDisplay([id](const auto& display) { return display.getId() == id; });
-    }
-
     std::vector<PhysicalDisplayId> getPhysicalDisplayIdsLocked() const REQUIRES(mStateLock);
 
     // mark a region of a layer stack dirty. this updates the dirty
@@ -973,7 +974,7 @@
     void setCompositorTimingSnapped(const DisplayStatInfo& stats,
                                     nsecs_t compositeToPresentLatency);
 
-    void postFrame();
+    void postFrame() REQUIRES(kMainThreadContext);
 
     /*
      * Display management
@@ -1037,8 +1038,8 @@
      */
     sp<IBinder> getPhysicalDisplayTokenLocked(PhysicalDisplayId displayId) const
             REQUIRES(mStateLock) {
-        const auto it = mPhysicalDisplayTokens.find(displayId);
-        return it != mPhysicalDisplayTokens.end() ? it->second : nullptr;
+        const sp<IBinder> nullToken;
+        return mPhysicalDisplayTokens.get(displayId).value_or(std::cref(nullToken));
     }
 
     std::optional<PhysicalDisplayId> getPhysicalDisplayIdLocked(
@@ -1051,18 +1052,17 @@
         return {};
     }
 
-    // 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.)
+    // Returns the first display connected at boot.
     //
-    // 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) {
-        return getPhysicalDisplayTokenLocked(getInternalDisplayIdLocked());
+    // TODO(b/229851933): SF conflates the primary display with the first display connected at boot,
+    // which typically has DisplayConnectionType::Internal. (Theoretically, it must be an internal
+    // display because SF does not support disconnecting it, though in practice HWC may circumvent
+    // this limitation.)
+    sp<IBinder> getPrimaryDisplayTokenLocked() const REQUIRES(mStateLock) {
+        return getPhysicalDisplayTokenLocked(getPrimaryDisplayIdLocked());
     }
 
-    PhysicalDisplayId getInternalDisplayIdLocked() const REQUIRES(mStateLock) {
+    PhysicalDisplayId getPrimaryDisplayIdLocked() const REQUIRES(mStateLock) {
         return getHwComposer().getPrimaryDisplayId();
     }
 
@@ -1088,15 +1088,19 @@
     void clearStatsLocked(const DumpArgs& args, std::string& result);
     void dumpTimeStats(const DumpArgs& args, bool asProto, std::string& result) const;
     void dumpFrameTimeline(const DumpArgs& args, std::string& result) const;
-    void logFrameStats();
+    void logFrameStats() REQUIRES(kMainThreadContext);
 
     void dumpVSync(std::string& result) const REQUIRES(mStateLock);
     void dumpStaticScreenStats(std::string& result) const;
     // Not const because each Layer needs to query Fences and cache timestamps.
     void dumpFrameEventsLocked(std::string& result);
+
+    void dumpCompositionDisplays(std::string& result) const REQUIRES(mStateLock);
+    void dumpDisplays(std::string& result) const REQUIRES(mStateLock);
     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 = LayerTracing::TRACE_ALL) const;
@@ -1233,10 +1237,14 @@
 
     std::vector<HotplugEvent> mPendingHotplugEvents GUARDED_BY(mStateLock);
 
-    // this may only be written from the main thread with mStateLock held
-    // it may be read from other threads with mStateLock held
-    std::map<wp<IBinder>, sp<DisplayDevice>> mDisplays GUARDED_BY(mStateLock);
-    std::unordered_map<PhysicalDisplayId, sp<IBinder>> mPhysicalDisplayTokens
+    // Displays are composited in `mDisplays` order. Internal displays are inserted at boot and
+    // never removed, so take precedence over external and virtual displays.
+    //
+    // The static capacities were chosen to exceed a typical number of physical/virtual displays.
+    //
+    // May be read from any thread, but must only be written from the main thread.
+    ftl::SmallMap<wp<IBinder>, const sp<DisplayDevice>, 5> mDisplays GUARDED_BY(mStateLock);
+    ftl::SmallMap<PhysicalDisplayId, const sp<IBinder>, 3> mPhysicalDisplayTokens
             GUARDED_BY(mStateLock);
 
     struct {
@@ -1369,7 +1377,7 @@
     sp<os::IInputFlinger> mInputFlinger;
     InputWindowCommands mInputWindowCommands;
 
-    Hwc2::impl::PowerAdvisor mPowerAdvisor;
+    std::unique_ptr<Hwc2::PowerAdvisor> mPowerAdvisor;
 
     void enableRefreshRateOverlay(bool enable) REQUIRES(mStateLock);
 
@@ -1409,10 +1417,8 @@
     std::atomic<ui::Transform::RotationFlags> mActiveDisplayTransformHint;
 
     bool isRefreshRateOverlayEnabled() const REQUIRES(mStateLock) {
-        return std::any_of(mDisplays.begin(), mDisplays.end(),
-                           [](std::pair<wp<IBinder>, sp<DisplayDevice>> display) {
-                               return display.second->isRefreshRateOverlayEnabled();
-                           });
+        return hasDisplay(
+                [](const auto& display) { return display.isRefreshRateOverlayEnabled(); });
     }
 
     wp<IBinder> mActiveDisplayToken GUARDED_BY(mStateLock);
@@ -1431,7 +1437,7 @@
         nsecs_t commitStart;
         nsecs_t compositeStart;
         nsecs_t presentEnd;
-    } mPowerHintSessionData GUARDED_BY(SF_MAIN_THREAD);
+    } mPowerHintSessionData GUARDED_BY(kMainThreadContext);
 
     nsecs_t mAnimationTransactionTimeout = s2ns(5);
 
@@ -1442,11 +1448,43 @@
 public:
     SurfaceComposerAIDL(sp<SurfaceFlinger> sf) { mFlinger = sf; }
 
+    binder::Status createDisplay(const std::string& displayName, bool secure,
+                                 sp<IBinder>* outDisplay) override;
+    binder::Status destroyDisplay(const sp<IBinder>& display) override;
+    binder::Status getPhysicalDisplayIds(std::vector<int64_t>* outDisplayIds) override;
+    binder::Status getPrimaryPhysicalDisplayId(int64_t* outDisplayId) override;
+    binder::Status getPhysicalDisplayToken(int64_t displayId, sp<IBinder>* outDisplay) override;
+    binder::Status setPowerMode(const sp<IBinder>& display, int mode) override;
+    binder::Status getDisplayStats(const sp<IBinder>& display,
+                                   gui::DisplayStatInfo* outStatInfo) override;
+    binder::Status getDisplayState(const sp<IBinder>& display,
+                                   gui::DisplayState* outState) override;
+    binder::Status clearBootDisplayMode(const sp<IBinder>& display) override;
+    binder::Status getBootDisplayModeSupport(bool* outMode) override;
+    binder::Status setAutoLowLatencyMode(const sp<IBinder>& display, bool on) override;
+    binder::Status setGameContentType(const sp<IBinder>& display, bool on) override;
     binder::Status captureDisplay(const DisplayCaptureArgs&,
                                   const sp<IScreenCaptureListener>&) override;
     binder::Status captureDisplayById(int64_t, const sp<IScreenCaptureListener>&) override;
     binder::Status captureLayers(const LayerCaptureArgs&,
                                  const sp<IScreenCaptureListener>&) override;
+    binder::Status isWideColorDisplay(const sp<IBinder>& token,
+                                      bool* outIsWideColorDisplay) override;
+    binder::Status getDisplayBrightnessSupport(const sp<IBinder>& displayToken,
+                                               bool* outSupport) override;
+    binder::Status setDisplayBrightness(const sp<IBinder>& displayToken,
+                                        const gui::DisplayBrightness& brightness) override;
+    binder::Status addHdrLayerInfoListener(const sp<IBinder>& displayToken,
+                                           const sp<gui::IHdrLayerInfoListener>& listener) override;
+    binder::Status removeHdrLayerInfoListener(
+            const sp<IBinder>& displayToken,
+            const sp<gui::IHdrLayerInfoListener>& listener) override;
+    binder::Status notifyPowerBoost(int boostId) override;
+
+private:
+    static const constexpr bool kUsePermissionCache = true;
+    status_t checkAccessPermission(bool usePermissionCache = kUsePermissionCache);
+    status_t checkControlDisplayBrightnessPermission();
 
 private:
     sp<SurfaceFlinger> mFlinger;
diff --git a/services/surfaceflinger/ThreadContext.h b/services/surfaceflinger/ThreadContext.h
new file mode 100644
index 0000000..85c379d
--- /dev/null
+++ b/services/surfaceflinger/ThreadContext.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace android {
+
+// Enforces exclusive access by the main thread.
+constexpr class [[clang::capability("mutex")]] {
+} kMainThreadContext;
+
+} // namespace android
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index b1a2bda..e5a9dd4 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -321,22 +321,19 @@
     mTimeStats.missedFramesLegacy++;
 }
 
-void TimeStats::incrementClientCompositionFrames() {
-    if (!mEnabled.load()) return;
+void TimeStats::pushCompositionStrategyState(const TimeStats::ClientCompositionRecord& record) {
+    if (!mEnabled.load() || !record.hasInterestingData()) {
+        return;
+    }
 
     ATRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mMutex);
-    mTimeStats.clientCompositionFramesLegacy++;
-}
-
-void TimeStats::incrementClientCompositionReusedFrames() {
-    if (!mEnabled.load()) return;
-
-    ATRACE_CALL();
-
-    std::lock_guard<std::mutex> lock(mMutex);
-    mTimeStats.clientCompositionReusedFramesLegacy++;
+    if (record.changed) mTimeStats.compositionStrategyChangesLegacy++;
+    if (record.hadClientComposition) mTimeStats.clientCompositionFramesLegacy++;
+    if (record.reused) mTimeStats.clientCompositionReusedFramesLegacy++;
+    if (record.predicted) mTimeStats.compositionStrategyPredictedLegacy++;
+    if (record.predictionSucceeded) mTimeStats.compositionStrategyPredictionSucceededLegacy++;
 }
 
 void TimeStats::incrementRefreshRateSwitches() {
@@ -348,15 +345,6 @@
     mTimeStats.refreshRateSwitchesLegacy++;
 }
 
-void TimeStats::incrementCompositionStrategyChanges() {
-    if (!mEnabled.load()) return;
-
-    ATRACE_CALL();
-
-    std::lock_guard<std::mutex> lock(mMutex);
-    mTimeStats.compositionStrategyChangesLegacy++;
-}
-
 void TimeStats::recordDisplayEventConnectionCount(int32_t count) {
     if (!mEnabled.load()) return;
 
@@ -1062,8 +1050,10 @@
     mTimeStats.missedFramesLegacy = 0;
     mTimeStats.clientCompositionFramesLegacy = 0;
     mTimeStats.clientCompositionReusedFramesLegacy = 0;
-    mTimeStats.refreshRateSwitchesLegacy = 0;
     mTimeStats.compositionStrategyChangesLegacy = 0;
+    mTimeStats.compositionStrategyPredictedLegacy = 0;
+    mTimeStats.compositionStrategyPredictionSucceededLegacy = 0;
+    mTimeStats.refreshRateSwitchesLegacy = 0;
     mTimeStats.displayEventConnectionsCountLegacy = 0;
     mTimeStats.displayOnTimeLegacy = 0;
     mTimeStats.presentToPresentLegacy.hist.clear();
diff --git a/services/surfaceflinger/TimeStats/TimeStats.h b/services/surfaceflinger/TimeStats/TimeStats.h
index 77c7973..7a159b8 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.h
+++ b/services/surfaceflinger/TimeStats/TimeStats.h
@@ -45,7 +45,7 @@
     virtual ~TimeStats() = default;
 
     // Process a pull request from statsd.
-    virtual bool onPullAtom(const int atomId, std::string* pulledData);
+    virtual bool onPullAtom(const int atomId, std::string* pulledData) = 0;
 
     virtual void parseArgs(bool asProto, const Vector<String16>& args, std::string& result) = 0;
     virtual bool isEnabled() = 0;
@@ -53,14 +53,8 @@
 
     virtual void incrementTotalFrames() = 0;
     virtual void incrementMissedFrames() = 0;
-    virtual void incrementClientCompositionFrames() = 0;
-    virtual void incrementClientCompositionReusedFrames() = 0;
     // Increments the number of times the display refresh rate changed.
     virtual void incrementRefreshRateSwitches() = 0;
-    // Increments the number of changes in composition strategy
-    // The intention is to reflect the number of changes between hwc and gpu
-    // composition, where "gpu composition" may also include mixed composition.
-    virtual void incrementCompositionStrategyChanges() = 0;
     // Records the most up-to-date count of display event connections.
     // The stored count will be the maximum ever recoded.
     virtual void recordDisplayEventConnectionCount(int32_t count) = 0;
@@ -158,6 +152,24 @@
         }
     };
 
+    struct ClientCompositionRecord {
+        // Frame had client composition or mixed composition
+        bool hadClientComposition = false;
+        // Composition changed between hw composition and mixed/client composition
+        bool changed = false;
+        // Frame reused the client composition result from a previous frame
+        bool reused = false;
+        // Composition strategy predicted for frame
+        bool predicted = false;
+        // Composition strategy prediction succeeded
+        bool predictionSucceeded = false;
+
+        // Whether there is data we want to record.
+        bool hasInterestingData() const {
+            return hadClientComposition || changed || reused || predicted;
+        }
+    };
+
     virtual void incrementJankyFrames(const JankyFramesInfo& info) = 0;
     // Clean up the layer record
     virtual void onDestroy(int32_t layerId) = 0;
@@ -169,6 +181,7 @@
     // Source of truth is RefrehRateStats.
     virtual void recordRefreshRate(uint32_t fps, nsecs_t duration) = 0;
     virtual void setPresentFenceGlobal(const std::shared_ptr<FenceTime>& presentFence) = 0;
+    virtual void pushCompositionStrategyState(const ClientCompositionRecord&) = 0;
 };
 
 namespace impl {
@@ -236,10 +249,7 @@
 
     void incrementTotalFrames() override;
     void incrementMissedFrames() override;
-    void incrementClientCompositionFrames() override;
-    void incrementClientCompositionReusedFrames() override;
     void incrementRefreshRateSwitches() override;
-    void incrementCompositionStrategyChanges() override;
     void recordDisplayEventConnectionCount(int32_t count) override;
 
     void recordFrameDuration(nsecs_t startTime, nsecs_t endTime) override;
@@ -275,6 +285,8 @@
     void recordRefreshRate(uint32_t fps, nsecs_t duration) override;
     void setPresentFenceGlobal(const std::shared_ptr<FenceTime>& presentFence) override;
 
+    void pushCompositionStrategyState(const ClientCompositionRecord&) override;
+
     static const size_t MAX_NUM_TIME_RECORDS = 64;
 
 private:
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
index 69afa2a..cf1ca65 100644
--- a/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
+++ b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
@@ -143,6 +143,14 @@
                   clientCompositionReusedFramesLegacy);
     StringAppendF(&result, "refreshRateSwitches = %d\n", refreshRateSwitchesLegacy);
     StringAppendF(&result, "compositionStrategyChanges = %d\n", compositionStrategyChangesLegacy);
+    StringAppendF(&result, "compositionStrategyPredicted = %d\n",
+                  compositionStrategyPredictedLegacy);
+    StringAppendF(&result, "compositionStrategyPredictionSucceeded = %d\n",
+                  compositionStrategyPredictionSucceededLegacy);
+    StringAppendF(&result, "compositionStrategyPredictionFailed = %d\n",
+                  compositionStrategyPredictedLegacy -
+                          compositionStrategyPredictionSucceededLegacy);
+
     StringAppendF(&result, "displayOnTime = %" PRId64 " ms\n", displayOnTimeLegacy);
     StringAppendF(&result, "displayConfigStats is as below:\n");
     for (const auto& [fps, duration] : refreshRateStatsLegacy) {
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
index 438561c..237ae8d 100644
--- a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
+++ b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
@@ -178,6 +178,8 @@
         Histogram frameDurationLegacy;
         Histogram renderEngineTimingLegacy;
         std::unordered_map<uint32_t, nsecs_t> refreshRateStatsLegacy;
+        int32_t compositionStrategyPredictedLegacy = 0;
+        int32_t compositionStrategyPredictionSucceededLegacy = 0;
 
         std::unordered_map<TimelineStatsKey, TimelineStats, TimelineStatsKey::Hasher> stats;
 
diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
index d249b60..a73eccf 100644
--- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
+++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
@@ -429,7 +429,7 @@
                                                   bufferProto.height(), bufferProto.pixel_format(),
                                                   bufferProto.usage()));
         layer.bufferData->frameNumber = bufferProto.frame_number();
-        layer.bufferData->flags = Flags<BufferData::BufferDataChange>(bufferProto.flags());
+        layer.bufferData->flags = ftl::Flags<BufferData::BufferDataChange>(bufferProto.flags());
         layer.bufferData->cachedBuffer.id = bufferProto.cached_buffer_id();
         layer.bufferData->acquireFence = Fence::NO_FENCE;
     }
diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h
index 04ca347..bab5326 100644
--- a/services/surfaceflinger/TransactionState.h
+++ b/services/surfaceflinger/TransactionState.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 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.
@@ -16,12 +16,21 @@
 
 #pragma once
 
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include <vector>
+
 #include <gui/LayerState.h>
+#include <system/window.h>
 
 namespace android {
+
 class CountDownLatch;
 
 struct TransactionState {
+    TransactionState() = default;
+
     TransactionState(const FrameTimelineInfo& frameTimelineInfo,
                      const Vector<ComposerState>& composerStates,
                      const Vector<DisplayState>& displayStates, uint32_t transactionFlags,
@@ -47,9 +56,30 @@
             originUid(originUid),
             id(transactionId) {}
 
-    TransactionState() {}
+    // Invokes `void(const layer_state_t&)` visitor for matching layers.
+    template <typename Visitor>
+    void traverseStatesWithBuffers(Visitor&& visitor) const {
+        for (const auto& [state] : states) {
+            if (state.hasBufferChanges() && state.hasValidBuffer() && state.surface) {
+                visitor(state);
+            }
+        }
+    }
 
-    void traverseStatesWithBuffers(std::function<void(const layer_state_t&)> visitor);
+    // TODO(b/185535769): Remove FrameHint. Instead, reset the idle timer (of the relevant physical
+    // display) on the main thread if commit leads to composite. Then, RefreshRateOverlay should be
+    // able to setFrameRate once, rather than for each transaction.
+    bool isFrameActive() const {
+        if (!displays.empty()) return true;
+
+        for (const auto& [state] : states) {
+            if (state.frameRateCompatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE) {
+                return true;
+            }
+        }
+
+        return false;
+    }
 
     FrameTimelineInfo frameTimelineInfo;
     Vector<ComposerState> states;
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
index 3c4ab95..a605a2f 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
@@ -358,6 +358,7 @@
     composer->setLayerPerFrameMetadataBlobs(display, outLayer, std::vector<PerFrameMetadataBlob>{});
 
     composer->setDisplayBrightness(display, mFdp.ConsumeFloatingPoint<float>(),
+                                   mFdp.ConsumeFloatingPoint<float>(),
                                    Hwc2::Composer::DisplayBrightnessOptions{
                                            .applyImmediately = mFdp.ConsumeIntegral<bool>()});
 }
@@ -585,6 +586,7 @@
     getDisplayedContentSample(halDisplayID);
 
     mHwc.setDisplayBrightness(mPhysicalDisplayId, mFdp.ConsumeFloatingPoint<float>(),
+                              mFdp.ConsumeFloatingPoint<float>(),
                               Hwc2::Composer::DisplayBrightnessOptions{
                                       .applyImmediately = mFdp.ConsumeIntegral<bool>()});
 
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index 1d0f62b..a80aca2 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -22,6 +22,7 @@
 #include <compositionengine/impl/CompositionEngine.h>
 #include <compositionengine/impl/Display.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
+#include <ftl/fake_guard.h>
 #include <gui/LayerDebugInfo.h>
 #include <gui/ScreenCaptureResults.h>
 #include <gui/SurfaceComposerClient.h>
@@ -50,6 +51,7 @@
 #include "SurfaceFlinger.h"
 #include "SurfaceFlingerDefaultFactory.h"
 #include "SurfaceInterceptor.h"
+#include "ThreadContext.h"
 #include "TimeStats/TimeStats.h"
 
 #include "renderengine/mock/RenderEngine.h"
@@ -445,7 +447,7 @@
         mFlinger->clearStatsLocked(dumpArgs, result);
 
         mFlinger->dumpTimeStats(dumpArgs, fdp->ConsumeBool(), result);
-        mFlinger->logFrameStats();
+        FTL_FAKE_GUARD(kMainThreadContext, mFlinger->logFrameStats());
 
         result = fdp->ConsumeRandomLengthString().c_str();
         mFlinger->dumpFrameTimeline(dumpArgs, result);
@@ -651,7 +653,7 @@
         updateCompositorTiming(&mFdp);
 
         mFlinger->setCompositorTimingSnapped({}, mFdp.ConsumeIntegral<nsecs_t>());
-        mFlinger->postFrame();
+        FTL_FAKE_GUARD(kMainThreadContext, mFlinger->postFrame());
         mFlinger->calculateExpectedPresentTime({});
 
         mFlinger->enableHalVirtualDisplays(mFdp.ConsumeBool());
diff --git a/services/surfaceflinger/tests/BootDisplayMode_test.cpp b/services/surfaceflinger/tests/BootDisplayMode_test.cpp
index abdb16d..d70908e 100644
--- a/services/surfaceflinger/tests/BootDisplayMode_test.cpp
+++ b/services/surfaceflinger/tests/BootDisplayMode_test.cpp
@@ -20,28 +20,33 @@
 
 #include <gui/SurfaceComposerClient.h>
 #include <private/gui/ComposerService.h>
+#include <private/gui/ComposerServiceAIDL.h>
 #include <chrono>
 
 namespace android {
 
 TEST(BootDisplayModeTest, setBootDisplayMode) {
     sp<ISurfaceComposer> sf(ComposerService::getComposerService());
+    sp<gui::ISurfaceComposer> sf_aidl(ComposerServiceAIDL::getComposerService());
     auto displayToken = SurfaceComposerClient::getInternalDisplayToken();
     bool bootModeSupport = false;
-    ASSERT_NO_FATAL_FAILURE(sf->getBootDisplayModeSupport(&bootModeSupport));
+    binder::Status status = sf_aidl->getBootDisplayModeSupport(&bootModeSupport);
+    ASSERT_NO_FATAL_FAILURE(status.transactionError());
     if (bootModeSupport) {
         ASSERT_EQ(NO_ERROR, sf->setBootDisplayMode(displayToken, 0));
     }
 }
 
 TEST(BootDisplayModeTest, clearBootDisplayMode) {
-    sp<ISurfaceComposer> sf(ComposerService::getComposerService());
+    sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
     auto displayToken = SurfaceComposerClient::getInternalDisplayToken();
     bool bootModeSupport = false;
-    ASSERT_NO_FATAL_FAILURE(sf->getBootDisplayModeSupport(&bootModeSupport));
+    binder::Status status = sf->getBootDisplayModeSupport(&bootModeSupport);
+    ASSERT_NO_FATAL_FAILURE(status.transactionError());
     if (bootModeSupport) {
-        ASSERT_EQ(NO_ERROR, sf->clearBootDisplayMode(displayToken));
+        status = sf->clearBootDisplayMode(displayToken);
+        ASSERT_EQ(NO_ERROR, status.transactionError());
     }
 }
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/surfaceflinger/tests/LayerCallback_test.cpp b/services/surfaceflinger/tests/LayerCallback_test.cpp
index 8a2305b..219db8c 100644
--- a/services/surfaceflinger/tests/LayerCallback_test.cpp
+++ b/services/surfaceflinger/tests/LayerCallback_test.cpp
@@ -55,24 +55,34 @@
         return createLayer(mClient, "test", 0, 0, ISurfaceComposerClient::eFXSurfaceBufferState);
     }
 
+    static int fillBuffer(Transaction& transaction, const sp<SurfaceControl>& layer,
+                          bool setBuffer = true, bool setBackgroundColor = false) {
+        sp<GraphicBuffer> buffer;
+        sp<Fence> fence;
+        if (setBuffer) {
+            int err = getBuffer(&buffer, &fence);
+            if (err != NO_ERROR) {
+                return err;
+            }
+
+            transaction.setBuffer(layer, buffer, fence);
+        }
+
+        if (setBackgroundColor) {
+            transaction.setBackgroundColor(layer, /*color*/ half3(1.0f, 0, 0), /*alpha*/ 1.0f,
+                                           ui::Dataspace::UNKNOWN);
+        }
+
+        return NO_ERROR;
+    }
+
     static int fillTransaction(Transaction& transaction, CallbackHelper* callbackHelper,
                                const sp<SurfaceControl>& layer = nullptr, bool setBuffer = true,
                                bool setBackgroundColor = false) {
         if (layer) {
-            sp<GraphicBuffer> buffer;
-            sp<Fence> fence;
-            if (setBuffer) {
-                int err = getBuffer(&buffer, &fence);
-                if (err != NO_ERROR) {
-                    return err;
-                }
-
-                transaction.setBuffer(layer, buffer, fence);
-            }
-
-            if (setBackgroundColor) {
-                transaction.setBackgroundColor(layer, /*color*/ half3(1.0f, 0, 0), /*alpha*/ 1.0f,
-                                               ui::Dataspace::UNKNOWN);
+            int err = fillBuffer(transaction, layer, setBuffer, setBackgroundColor);
+            if (err != NO_ERROR) {
+                return err;
             }
         }
 
@@ -1115,7 +1125,7 @@
     Transaction transaction;
     CallbackHelper callback;
     int err = fillTransaction(transaction, &callback, layer, true);
-    err |= fillTransaction(transaction, &callback, offscreenLayer, true);
+    err |= fillBuffer(transaction, offscreenLayer);
     if (err) {
         GTEST_SUCCEED() << "test not supported";
         return;
@@ -1129,5 +1139,86 @@
     committedSc.insert(layer);
     committedSc.insert(offscreenLayer);
     EXPECT_NO_FATAL_FAILURE(waitForCommitCallback(callback, committedSc));
+
+    ExpectedResult expected;
+    expected.addSurface(ExpectedResult::Transaction::PRESENTED, layer);
+    expected.addSurface(ExpectedResult::Transaction::PRESENTED, offscreenLayer);
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true));
 }
+
+TEST_F(LayerCallbackTest, TransactionCommittedCallback_BSL) {
+    sp<SurfaceControl> layer;
+    ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer());
+
+    Transaction transaction;
+    CallbackHelper callback;
+    int err = fillTransaction(transaction, &callback, layer, true);
+    if (err) {
+        GTEST_SUCCEED() << "test not supported";
+        return;
+    }
+    transaction.addTransactionCommittedCallback(callback.function, callback.getContext()).apply();
+    std::unordered_set<sp<SurfaceControl>, SCHash> committedSc;
+    committedSc.insert(layer);
+    EXPECT_NO_FATAL_FAILURE(waitForCommitCallback(callback, committedSc));
+    ExpectedResult expected;
+    expected.addSurface(ExpectedResult::Transaction::PRESENTED, layer);
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true));
+}
+
+TEST_F(LayerCallbackTest, TransactionCommittedCallback_EffectLayer) {
+    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.addTransactionCommittedCallback(callback.function, callback.getContext()).apply();
+    std::unordered_set<sp<SurfaceControl>, SCHash> committedSc;
+    EXPECT_NO_FATAL_FAILURE(waitForCommitCallback(callback, committedSc));
+
+    ExpectedResult expected;
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true));
+}
+
+TEST_F(LayerCallbackTest, TransactionCommittedCallback_ContainerLayer) {
+    sp<SurfaceControl> layer;
+    ASSERT_NO_FATAL_FAILURE(layer = createLayer(mClient, "Container Layer", 0, 0,
+                                                ISurfaceComposerClient::eFXSurfaceContainer));
+
+    Transaction transaction;
+    CallbackHelper callback;
+    int err = fillTransaction(transaction, &callback);
+    if (err) {
+        GTEST_SUCCEED() << "test not supported";
+        return;
+    }
+    transaction.addTransactionCommittedCallback(callback.function, callback.getContext()).apply();
+    std::unordered_set<sp<SurfaceControl>, SCHash> committedSc;
+    EXPECT_NO_FATAL_FAILURE(waitForCommitCallback(callback, committedSc));
+
+    ExpectedResult expected;
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true));
+}
+
+TEST_F(LayerCallbackTest, TransactionCommittedCallback_NoLayer) {
+    Transaction transaction;
+    CallbackHelper callback;
+    int err = fillTransaction(transaction, &callback);
+    if (err) {
+        GTEST_SUCCEED() << "test not supported";
+        return;
+    }
+    transaction.addTransactionCommittedCallback(callback.function, callback.getContext()).apply();
+    std::unordered_set<sp<SurfaceControl>, SCHash> committedSc;
+    EXPECT_NO_FATAL_FAILURE(waitForCommitCallback(callback, committedSc));
+
+    ExpectedResult expected;
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true));
+}
+
 } // namespace android
diff --git a/services/surfaceflinger/tests/fakehwc/Android.bp b/services/surfaceflinger/tests/fakehwc/Android.bp
index 168b576..704815d 100644
--- a/services/surfaceflinger/tests/fakehwc/Android.bp
+++ b/services/surfaceflinger/tests/fakehwc/Android.bp
@@ -28,6 +28,7 @@
         "android.hardware.graphics.mapper@3.0",
         "android.hardware.graphics.mapper@4.0",
         "android.hardware.power@1.3",
+        "android.hardware.power-V2-cpp",
         "libbase",
         "libbinder",
         "libbinder_ndk",
diff --git a/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp b/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp
new file mode 100644
index 0000000..9ab35d7
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp
@@ -0,0 +1,310 @@
+/*
+ * 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 "AidlPowerHalWrapperTest"
+
+#include <android-base/stringprintf.h>
+#include <android/hardware/power/IPower.h>
+#include <android/hardware/power/IPowerHintSession.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <algorithm>
+#include <chrono>
+#include <memory>
+#include "DisplayHardware/PowerAdvisor.h"
+#include "android/hardware/power/WorkDuration.h"
+#include "binder/Status.h"
+#include "log/log_main.h"
+#include "mock/DisplayHardware/MockIPower.h"
+#include "mock/DisplayHardware/MockIPowerHintSession.h"
+#include "utils/Timers.h"
+
+using namespace android;
+using namespace android::Hwc2::mock;
+using namespace android::hardware::power;
+using namespace std::chrono_literals;
+using namespace testing;
+
+namespace android::Hwc2::impl {
+
+class AidlPowerHalWrapperTest : public testing::Test {
+public:
+    void SetUp() override;
+
+protected:
+    std::unique_ptr<AidlPowerHalWrapper> mWrapper = nullptr;
+    sp<NiceMock<MockIPower>> mMockHal = nullptr;
+    sp<NiceMock<MockIPowerHintSession>> mMockSession = nullptr;
+    void verifyAndClearExpectations();
+    void sendActualWorkDurationGroup(std::vector<WorkDuration> durations,
+                                     std::chrono::nanoseconds sleepBeforeLastSend);
+};
+
+void AidlPowerHalWrapperTest::SetUp() {
+    mMockHal = new NiceMock<MockIPower>();
+    mMockSession = new NiceMock<MockIPowerHintSession>();
+    ON_CALL(*mMockHal.get(), getHintSessionPreferredRate(_)).WillByDefault(Return(Status::ok()));
+    mWrapper = std::make_unique<AidlPowerHalWrapper>(mMockHal);
+}
+
+void AidlPowerHalWrapperTest::verifyAndClearExpectations() {
+    Mock::VerifyAndClearExpectations(mMockHal.get());
+    Mock::VerifyAndClearExpectations(mMockSession.get());
+}
+
+void AidlPowerHalWrapperTest::sendActualWorkDurationGroup(
+        std::vector<WorkDuration> durations, std::chrono::nanoseconds sleepBeforeLastSend) {
+    for (size_t i = 0; i < durations.size(); i++) {
+        if (i == durations.size() - 1) {
+            std::this_thread::sleep_for(sleepBeforeLastSend);
+        }
+        auto duration = durations[i];
+        mWrapper->sendActualWorkDuration(duration.durationNanos, duration.timeStampNanos);
+    }
+}
+WorkDuration toWorkDuration(std::chrono::nanoseconds durationNanos, int64_t timeStampNanos) {
+    WorkDuration duration;
+    duration.durationNanos = durationNanos.count();
+    duration.timeStampNanos = timeStampNanos;
+    return duration;
+}
+
+std::string printWorkDurations(const ::std::vector<WorkDuration>& durations) {
+    std::ostringstream os;
+    for (auto duration : durations) {
+        os << duration.toString();
+        os << "\n";
+    }
+    return os.str();
+}
+
+namespace {
+TEST_F(AidlPowerHalWrapperTest, supportsPowerHintSession) {
+    ASSERT_TRUE(mWrapper->supportsPowerHintSession());
+    Mock::VerifyAndClearExpectations(mMockHal.get());
+    ON_CALL(*mMockHal.get(), getHintSessionPreferredRate(_))
+            .WillByDefault(Return(Status::fromExceptionCode(Status::Exception::EX_ILLEGAL_STATE)));
+    auto newWrapper = AidlPowerHalWrapper(mMockHal);
+    EXPECT_FALSE(newWrapper.supportsPowerHintSession());
+}
+
+TEST_F(AidlPowerHalWrapperTest, startPowerHintSession) {
+    ASSERT_TRUE(mWrapper->supportsPowerHintSession());
+    std::vector<int32_t> threadIds = {1, 2};
+    mWrapper->setPowerHintSessionThreadIds(threadIds);
+    EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
+            .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
+    EXPECT_TRUE(mWrapper->startPowerHintSession());
+    EXPECT_FALSE(mWrapper->startPowerHintSession());
+}
+
+TEST_F(AidlPowerHalWrapperTest, restartNewPoserHintSessionWithNewThreadIds) {
+    ASSERT_TRUE(mWrapper->supportsPowerHintSession());
+
+    std::vector<int32_t> threadIds = {1, 2};
+    EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
+            .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
+    mWrapper->setPowerHintSessionThreadIds(threadIds);
+    EXPECT_EQ(mWrapper->getPowerHintSessionThreadIds(), threadIds);
+    ASSERT_TRUE(mWrapper->startPowerHintSession());
+    verifyAndClearExpectations();
+
+    threadIds = {2, 3};
+    EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
+            .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
+    EXPECT_CALL(*mMockSession.get(), close()).Times(1);
+    mWrapper->setPowerHintSessionThreadIds(threadIds);
+    EXPECT_EQ(mWrapper->getPowerHintSessionThreadIds(), threadIds);
+    verifyAndClearExpectations();
+
+    EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _)).Times(0);
+    EXPECT_CALL(*mMockSession.get(), close()).Times(0);
+    mWrapper->setPowerHintSessionThreadIds(threadIds);
+    verifyAndClearExpectations();
+}
+
+TEST_F(AidlPowerHalWrapperTest, setTargetWorkDuration) {
+    ASSERT_TRUE(mWrapper->supportsPowerHintSession());
+
+    std::vector<int32_t> threadIds = {1, 2};
+    mWrapper->setPowerHintSessionThreadIds(threadIds);
+    EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
+            .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
+    ASSERT_TRUE(mWrapper->startPowerHintSession());
+    verifyAndClearExpectations();
+
+    std::chrono::nanoseconds base = 100ms;
+    // test cases with target work duration and whether it should update hint against baseline 100ms
+    const std::vector<std::pair<std::chrono::nanoseconds, bool>> testCases = {{0ms, false},
+                                                                              {-1ms, false},
+                                                                              {200ms, true},
+                                                                              {2ms, true},
+                                                                              {91ms, false},
+                                                                              {109ms, false}};
+
+    for (const auto& test : testCases) {
+        // reset to 100ms baseline
+        mWrapper->setTargetWorkDuration(1);
+        mWrapper->setTargetWorkDuration(base.count());
+
+        auto target = test.first;
+        EXPECT_CALL(*mMockSession.get(), updateTargetWorkDuration(target.count()))
+                .Times(test.second ? 1 : 0);
+        mWrapper->setTargetWorkDuration(target.count());
+        verifyAndClearExpectations();
+    }
+}
+
+TEST_F(AidlPowerHalWrapperTest, setTargetWorkDuration_shouldReconnectOnError) {
+    ASSERT_TRUE(mWrapper->supportsPowerHintSession());
+
+    std::vector<int32_t> threadIds = {1, 2};
+    mWrapper->setPowerHintSessionThreadIds(threadIds);
+    EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
+            .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
+    ASSERT_TRUE(mWrapper->startPowerHintSession());
+    verifyAndClearExpectations();
+
+    EXPECT_CALL(*mMockSession.get(), updateTargetWorkDuration(1))
+            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_ILLEGAL_STATE)));
+    mWrapper->setTargetWorkDuration(1);
+    EXPECT_TRUE(mWrapper->shouldReconnectHAL());
+}
+
+TEST_F(AidlPowerHalWrapperTest, sendActualWorkDuration) {
+    ASSERT_TRUE(mWrapper->supportsPowerHintSession());
+
+    std::vector<int32_t> threadIds = {1, 2};
+    mWrapper->setPowerHintSessionThreadIds(threadIds);
+    EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
+            .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
+    ASSERT_TRUE(mWrapper->startPowerHintSession());
+    verifyAndClearExpectations();
+
+    auto base = toWorkDuration(100ms, 0);
+    // test cases with actual work durations and whether it should update hint against baseline
+    // 100ms
+    const std::vector<std::pair<std::vector<std::pair<std::chrono::nanoseconds, nsecs_t>>, bool>>
+            testCases = {{{{-1ms, 100}}, false},
+                         {{{91ms, 100}}, false},
+                         {{{109ms, 100}}, false},
+                         {{{100ms, 100}, {200ms, 200}}, true},
+                         {{{100ms, 500}, {100ms, 600}, {3ms, 600}}, true}};
+
+    for (const auto& test : testCases) {
+        // reset actual duration
+        sendActualWorkDurationGroup({base}, 80ms);
+
+        auto raw = test.first;
+        std::vector<WorkDuration> durations(raw.size());
+        std::transform(raw.begin(), raw.end(), durations.begin(),
+                       [](std::pair<std::chrono::nanoseconds, nsecs_t> d) {
+                           return toWorkDuration(d.first, d.second);
+                       });
+        EXPECT_CALL(*mMockSession.get(), reportActualWorkDuration(durations))
+                .Times(test.second ? 1 : 0);
+        sendActualWorkDurationGroup(durations, 0ms);
+        verifyAndClearExpectations();
+    }
+}
+
+TEST_F(AidlPowerHalWrapperTest, sendAdjustedActualWorkDuration) {
+    ASSERT_TRUE(mWrapper->supportsPowerHintSession());
+
+    std::vector<int32_t> threadIds = {1, 2};
+    mWrapper->setPowerHintSessionThreadIds(threadIds);
+    EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
+            .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
+    ASSERT_TRUE(mWrapper->startPowerHintSession());
+    verifyAndClearExpectations();
+
+    std::chrono::nanoseconds lastTarget = 100ms;
+    EXPECT_CALL(*mMockSession.get(), updateTargetWorkDuration(lastTarget.count())).Times(1);
+    mWrapper->setTargetWorkDuration(lastTarget.count());
+    std::chrono::nanoseconds newTarget = 105ms;
+    mWrapper->setTargetWorkDuration(newTarget.count());
+    EXPECT_CALL(*mMockSession.get(), updateTargetWorkDuration(newTarget.count())).Times(0);
+    std::chrono::nanoseconds actual = 21ms;
+    // 100 / 105 * 21ms = 20ms
+    std::chrono::nanoseconds expectedActualSent = 20ms;
+    std::vector<WorkDuration> expectedDurations = {toWorkDuration(expectedActualSent, 1)};
+
+    EXPECT_CALL(*mMockSession.get(), reportActualWorkDuration(_))
+            .WillOnce(DoAll(
+                    [expectedDurations](const ::std::vector<WorkDuration>& durationsSent) {
+                        EXPECT_EQ(expectedDurations, durationsSent)
+                                << base::StringPrintf("actual sent: %s vs expected: %s",
+                                                      printWorkDurations(durationsSent).c_str(),
+                                                      printWorkDurations(expectedDurations)
+                                                              .c_str());
+                    },
+                    Return(Status::ok())));
+    mWrapper->sendActualWorkDuration(actual.count(), 1);
+}
+
+TEST_F(AidlPowerHalWrapperTest, sendActualWorkDuration_exceedsStaleTime) {
+    ASSERT_TRUE(mWrapper->supportsPowerHintSession());
+
+    std::vector<int32_t> threadIds = {1, 2};
+    mWrapper->setPowerHintSessionThreadIds(threadIds);
+    EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
+            .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
+    ASSERT_TRUE(mWrapper->startPowerHintSession());
+    verifyAndClearExpectations();
+
+    auto base = toWorkDuration(100ms, 0);
+    // test cases with actual work durations and whether it should update hint against baseline
+    // 100ms
+    const std::vector<std::pair<std::vector<std::pair<std::chrono::nanoseconds, nsecs_t>>, bool>>
+            testCases = {{{{91ms, 100}}, true}, {{{109ms, 100}}, true}};
+
+    for (const auto& test : testCases) {
+        // reset actual duration
+        sendActualWorkDurationGroup({base}, 80ms);
+
+        auto raw = test.first;
+        std::vector<WorkDuration> durations(raw.size());
+        std::transform(raw.begin(), raw.end(), durations.begin(),
+                       [](std::pair<std::chrono::nanoseconds, nsecs_t> d) {
+                           return toWorkDuration(d.first, d.second);
+                       });
+        EXPECT_CALL(*mMockSession.get(), reportActualWorkDuration(durations))
+                .Times(test.second ? 1 : 0);
+        sendActualWorkDurationGroup(durations, 80ms);
+        verifyAndClearExpectations();
+    }
+}
+
+TEST_F(AidlPowerHalWrapperTest, sendActualWorkDuration_shouldReconnectOnError) {
+    ASSERT_TRUE(mWrapper->supportsPowerHintSession());
+
+    std::vector<int32_t> threadIds = {1, 2};
+    mWrapper->setPowerHintSessionThreadIds(threadIds);
+    EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
+            .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
+    ASSERT_TRUE(mWrapper->startPowerHintSession());
+    verifyAndClearExpectations();
+    WorkDuration duration;
+    duration.durationNanos = 1;
+    EXPECT_CALL(*mMockSession.get(), reportActualWorkDuration(_))
+            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_ILLEGAL_STATE)));
+    sendActualWorkDurationGroup({duration}, 0ms);
+    EXPECT_TRUE(mWrapper->shouldReconnectHAL());
+}
+
+} // namespace
+} // namespace android::Hwc2::impl
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 1eea023..7823363 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -26,6 +26,8 @@
     srcs: [
         "mock/DisplayHardware/MockComposer.cpp",
         "mock/DisplayHardware/MockHWC2.cpp",
+        "mock/DisplayHardware/MockIPower.cpp",
+        "mock/DisplayHardware/MockIPowerHintSession.cpp",
         "mock/DisplayHardware/MockPowerAdvisor.cpp",
         "mock/MockEventThread.cpp",
         "mock/MockFrameTimeline.cpp",
@@ -67,6 +69,7 @@
         ":libsurfaceflinger_mock_sources",
         ":libsurfaceflinger_sources",
         "libsurfaceflinger_unittest_main.cpp",
+        "AidlPowerHalWrapperTest.cpp",
         "CachingTest.cpp",
         "CompositionTest.cpp",
         "DispSyncSourceTest.cpp",
@@ -100,6 +103,7 @@
         "SurfaceFlinger_HotplugTest.cpp",
         "SurfaceFlinger_NotifyPowerBoostTest.cpp",
         "SurfaceFlinger_OnInitializeDisplaysTest.cpp",
+        "SurfaceFlinger_PowerHintTest.cpp",
         "SurfaceFlinger_SetDisplayStateTest.cpp",
         "SurfaceFlinger_SetPowerModeInternalTest.cpp",
         "SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 15c9d19..c541b92 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -113,8 +113,9 @@
         mFlinger.setupTimeStats(std::shared_ptr<TimeStats>(mTimeStats));
 
         mComposer = new Hwc2::mock::Composer();
+        mPowerAdvisor = new Hwc2::mock::PowerAdvisor();
         mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer));
-
+        mFlinger.setupPowerAdvisor(std::unique_ptr<Hwc2::PowerAdvisor>(mPowerAdvisor));
         mFlinger.mutableMaxRenderTargetSize() = 16384;
     }
 
@@ -188,7 +189,7 @@
     Hwc2::mock::Composer* mComposer = nullptr;
     renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
     mock::TimeStats* mTimeStats = new mock::TimeStats();
-    Hwc2::mock::PowerAdvisor mPowerAdvisor;
+    Hwc2::mock::PowerAdvisor* mPowerAdvisor = nullptr;
 
     sp<Fence> mClientTargetAcquireFence = Fence::NO_FENCE;
 
@@ -300,7 +301,7 @@
                                      .setId(DEFAULT_DISPLAY_ID)
                                      .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT})
                                      .setIsSecure(Derived::IS_SECURE)
-                                     .setPowerAdvisor(&test->mPowerAdvisor)
+                                     .setPowerAdvisor(test->mPowerAdvisor)
                                      .setName(std::string("Injected display for ") +
                                               test_info->test_case_name() + "." + test_info->name())
                                      .build();
diff --git a/services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp b/services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp
index 73c60e1..225ad16 100644
--- a/services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp
@@ -19,6 +19,7 @@
 
 #include "DisplayTransactionTestHelpers.h"
 
+#include <ftl/fake_guard.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
@@ -35,7 +36,7 @@
 };
 
 TEST_F(SetDisplayBrightnessTest, persistDisplayBrightnessNoComposite) {
-    MainThreadScopedGuard fakeMainThreadGuard(SF_MAIN_THREAD);
+    ftl::FakeGuard guard(kMainThreadContext);
     sp<DisplayDevice> displayDevice = getDisplayDevice();
 
     EXPECT_EQ(std::nullopt, displayDevice->getStagedBrightness());
@@ -52,7 +53,7 @@
 }
 
 TEST_F(SetDisplayBrightnessTest, persistDisplayBrightnessWithComposite) {
-    MainThreadScopedGuard fakeMainThreadGuard(SF_MAIN_THREAD);
+    ftl::FakeGuard guard(kMainThreadContext);
     sp<DisplayDevice> displayDevice = getDisplayDevice();
 
     EXPECT_EQ(std::nullopt, displayDevice->getStagedBrightness());
@@ -70,7 +71,7 @@
 }
 
 TEST_F(SetDisplayBrightnessTest, persistDisplayBrightnessWithCompositeShortCircuitsOnNoOp) {
-    MainThreadScopedGuard fakeMainThreadGuard(SF_MAIN_THREAD);
+    ftl::FakeGuard guard(kMainThreadContext);
     sp<DisplayDevice> displayDevice = getDisplayDevice();
 
     EXPECT_EQ(std::nullopt, displayDevice->getStagedBrightness());
diff --git a/services/surfaceflinger/tests/unittests/DisplayDevice_SetProjectionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayDevice_SetProjectionTest.cpp
index 3d24ecb..5734d34 100644
--- a/services/surfaceflinger/tests/unittests/DisplayDevice_SetProjectionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayDevice_SetProjectionTest.cpp
@@ -21,6 +21,7 @@
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <ui/Rotation.h>
 
 namespace android {
 namespace {
@@ -53,6 +54,11 @@
 
     ui::Size swapWH(const ui::Size size) const { return ui::Size(size.height, size.width); }
 
+    void setDefaultProjection() {
+        // INVALID_RECT pulls from the physical display dimensions.
+        mDisplayDevice->setProjection(ui::ROTATION_0, Rect::INVALID_RECT, Rect::INVALID_RECT);
+    }
+
     void setProjectionForRotation0() {
         // A logical rotation of 0 uses the SurfaceFlinger display size
         mDisplayDevice->setProjection(ui::ROTATION_0, Rect(mFlingerDisplaySize),
@@ -79,6 +85,30 @@
                                       Rect(swapWH(mFlingerDisplaySize)));
     }
 
+    void expectDefaultState() {
+        const auto& compositionState = mDisplayDevice->getCompositionDisplay()->getState();
+        EXPECT_EQ(ui::Transform(ui::Transform::toRotationFlags(mPhysicalOrientation),
+                                mHardwareDisplaySize.width, mHardwareDisplaySize.height),
+                  compositionState.transform);
+        EXPECT_EQ(mPhysicalOrientation, compositionState.displaySpace.getOrientation());
+        EXPECT_EQ(Rect(mHardwareDisplaySize), compositionState.displaySpace.getContent());
+        EXPECT_EQ(mHardwareDisplaySize, compositionState.displaySpace.getBounds());
+        EXPECT_EQ(Rect(mHardwareDisplaySize), compositionState.framebufferSpace.getContent());
+        EXPECT_EQ(mHardwareDisplaySize, compositionState.framebufferSpace.getBounds());
+
+        const ui::Size expectedLogicalSize = (mPhysicalOrientation == ui::ROTATION_270 ||
+                                              mPhysicalOrientation == ui::ROTATION_90)
+                ? swapWH(mHardwareDisplaySize)
+                : mHardwareDisplaySize;
+
+        EXPECT_EQ(Rect(expectedLogicalSize), compositionState.orientedDisplaySpace.getContent());
+        EXPECT_EQ(expectedLogicalSize, compositionState.orientedDisplaySpace.getBounds());
+        EXPECT_EQ(Rect(expectedLogicalSize), compositionState.layerStackSpace.getContent());
+        EXPECT_EQ(expectedLogicalSize, compositionState.layerStackSpace.getBounds());
+
+        EXPECT_EQ(false, compositionState.needsFiltering);
+    }
+
     void expectStateForHardwareTransform0() {
         const auto& compositionState = mDisplayDevice->getCompositionDisplay()->getState();
         EXPECT_EQ(ui::Transform(TRANSFORM_FLAGS_ROT_0, mHardwareDisplaySize.width,
@@ -147,6 +177,11 @@
                                            ui::ROTATION_0) {}
 };
 
+TEST_F(DisplayDeviceSetProjectionTest_Installed0, checkDefaultProjection) {
+    setDefaultProjection();
+    expectDefaultState();
+}
+
 TEST_F(DisplayDeviceSetProjectionTest_Installed0, checkWith0OutputRotation) {
     setProjectionForRotation0();
     expectStateForHardwareTransform0();
@@ -174,6 +209,11 @@
                                            ui::ROTATION_90) {}
 };
 
+TEST_F(DisplayDeviceSetProjectionTest_Installed90, checkDefaultProjection) {
+    setDefaultProjection();
+    expectDefaultState();
+}
+
 TEST_F(DisplayDeviceSetProjectionTest_Installed90, checkWith0OutputRotation) {
     setProjectionForRotation0();
     expectStateForHardwareTransform90();
@@ -201,6 +241,11 @@
                                            ui::ROTATION_180) {}
 };
 
+TEST_F(DisplayDeviceSetProjectionTest_Installed180, checkDefaultProjection) {
+    setDefaultProjection();
+    expectDefaultState();
+}
+
 TEST_F(DisplayDeviceSetProjectionTest_Installed180, checkWith0OutputRotation) {
     setProjectionForRotation0();
     expectStateForHardwareTransform180();
@@ -228,6 +273,11 @@
                                            ui::ROTATION_270) {}
 };
 
+TEST_F(DisplayDeviceSetProjectionTest_Installed270, checkDefaultProjection) {
+    setDefaultProjection();
+    expectDefaultState();
+}
+
 TEST_F(DisplayDeviceSetProjectionTest_Installed270, checkWith0OutputRotation) {
     setProjectionForRotation0();
     expectStateForHardwareTransform270();
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index 2425862..f04221c 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -165,36 +165,39 @@
     return displayDevice;
 }
 
-bool DisplayTransactionTest::hasPhysicalHwcDisplay(HWDisplayId hwcDisplayId) {
-    return mFlinger.mutableHwcPhysicalDisplayIdMap().count(hwcDisplayId) == 1;
+bool DisplayTransactionTest::hasPhysicalHwcDisplay(HWDisplayId hwcDisplayId) const {
+    return mFlinger.hwcPhysicalDisplayIdMap().count(hwcDisplayId) == 1;
 }
 
-bool DisplayTransactionTest::hasTransactionFlagSet(int flag) {
-    return mFlinger.mutableTransactionFlags() & flag;
+bool DisplayTransactionTest::hasTransactionFlagSet(int32_t flag) const {
+    return mFlinger.transactionFlags() & flag;
 }
 
-bool DisplayTransactionTest::hasDisplayDevice(sp<IBinder> displayToken) {
-    return mFlinger.mutableDisplays().count(displayToken) == 1;
+bool DisplayTransactionTest::hasDisplayDevice(const sp<IBinder>& displayToken) const {
+    return mFlinger.displays().contains(displayToken);
 }
 
-sp<DisplayDevice> DisplayTransactionTest::getDisplayDevice(sp<IBinder> displayToken) {
-    return mFlinger.mutableDisplays()[displayToken];
+const DisplayDevice& DisplayTransactionTest::getDisplayDevice(
+        const sp<IBinder>& displayToken) const {
+    return *mFlinger.displays().get(displayToken)->get();
 }
 
-bool DisplayTransactionTest::hasCurrentDisplayState(sp<IBinder> displayToken) {
-    return mFlinger.mutableCurrentState().displays.indexOfKey(displayToken) >= 0;
+bool DisplayTransactionTest::hasCurrentDisplayState(const sp<IBinder>& displayToken) const {
+    return mFlinger.currentState().displays.indexOfKey(displayToken) >= 0;
 }
 
-const DisplayDeviceState& DisplayTransactionTest::getCurrentDisplayState(sp<IBinder> displayToken) {
-    return mFlinger.mutableCurrentState().displays.valueFor(displayToken);
+const DisplayDeviceState& DisplayTransactionTest::getCurrentDisplayState(
+        const sp<IBinder>& displayToken) const {
+    return mFlinger.currentState().displays.valueFor(displayToken);
 }
 
-bool DisplayTransactionTest::hasDrawingDisplayState(sp<IBinder> displayToken) {
-    return mFlinger.mutableDrawingState().displays.indexOfKey(displayToken) >= 0;
+bool DisplayTransactionTest::hasDrawingDisplayState(const sp<IBinder>& displayToken) const {
+    return mFlinger.drawingState().displays.indexOfKey(displayToken) >= 0;
 }
 
-const DisplayDeviceState& DisplayTransactionTest::getDrawingDisplayState(sp<IBinder> displayToken) {
-    return mFlinger.mutableDrawingState().displays.valueFor(displayToken);
+const DisplayDeviceState& DisplayTransactionTest::getDrawingDisplayState(
+        const sp<IBinder>& displayToken) const {
+    return mFlinger.drawingState().displays.valueFor(displayToken);
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
index 565c244..f5235ce 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
@@ -95,14 +95,17 @@
     // --------------------------------------------------------------------
     // Postcondition helpers
 
-    bool hasPhysicalHwcDisplay(hal::HWDisplayId hwcDisplayId);
-    bool hasTransactionFlagSet(int flag);
-    bool hasDisplayDevice(sp<IBinder> displayToken);
-    sp<DisplayDevice> getDisplayDevice(sp<IBinder> displayToken);
-    bool hasCurrentDisplayState(sp<IBinder> displayToken);
-    const DisplayDeviceState& getCurrentDisplayState(sp<IBinder> displayToken);
-    bool hasDrawingDisplayState(sp<IBinder> displayToken);
-    const DisplayDeviceState& getDrawingDisplayState(sp<IBinder> displayToken);
+    bool hasPhysicalHwcDisplay(hal::HWDisplayId) const;
+    bool hasTransactionFlagSet(int32_t flag) const;
+
+    bool hasDisplayDevice(const sp<IBinder>& displayToken) const;
+    const DisplayDevice& getDisplayDevice(const sp<IBinder>& displayToken) const;
+
+    bool hasCurrentDisplayState(const sp<IBinder>& displayToken) const;
+    const DisplayDeviceState& getCurrentDisplayState(const sp<IBinder>& displayToken) const;
+
+    bool hasDrawingDisplayState(const sp<IBinder>& displayToken) const;
+    const DisplayDeviceState& getDrawingDisplayState(const sp<IBinder>& displayToken) const;
 
     // --------------------------------------------------------------------
     // Test instances
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index 14d8f98..c033af8 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -34,12 +34,13 @@
 using namespace std::chrono_literals;
 using namespace std::placeholders;
 
-using namespace android::flag_operators;
 using testing::_;
 using testing::Invoke;
 
 namespace android {
 
+using namespace ftl::flag_operators;
+
 namespace {
 
 constexpr PhysicalDisplayId INTERNAL_DISPLAY_ID = PhysicalDisplayId::fromPort(111u);
@@ -414,6 +415,10 @@
     EXPECT_CALL(*mVSyncSource, getLatestVSyncData()).WillOnce(Return(preferredData));
 
     VsyncEventData vsyncEventData = mThread->getLatestVsyncEventData(mConnection);
+
+    // Check EventThread immediately requested a resync.
+    EXPECT_TRUE(mResyncCallRecorder.waitForCall().has_value());
+
     EXPECT_GT(vsyncEventData.frameTimelines[0].deadlineTimestamp, now)
             << "Deadline timestamp should be greater than frame time";
     for (size_t i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
index 825f145..b9a5f36 100644
--- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
@@ -337,10 +337,22 @@
                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS, ""));
     EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+
+    // Privileged APIs.
+    EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT,
+                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+    EXPECT_FALSE(ValidateFrameRate(0.0f, ANATIVEWINDOW_FRAME_RATE_NO_VOTE,
+                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+
+    constexpr bool kPrivileged = true;
     EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT,
                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "",
-                                  /*privileged=*/true));
+                                  kPrivileged));
+    EXPECT_TRUE(ValidateFrameRate(0.0f, ANATIVEWINDOW_FRAME_RATE_NO_VOTE,
+                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "",
+                                  kPrivileged));
 
+    // Invalid frame rate.
     EXPECT_FALSE(ValidateFrameRate(-1, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
                                    ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
     EXPECT_FALSE(ValidateFrameRate(1.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
@@ -348,15 +360,12 @@
     EXPECT_FALSE(ValidateFrameRate(0.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
                                    ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
 
-    EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT,
-                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-
-    // Invalid compatibility
+    // Invalid compatibility.
     EXPECT_FALSE(
             ValidateFrameRate(60.0f, -1, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
     EXPECT_FALSE(ValidateFrameRate(60.0f, 2, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
 
-    // Invalid change frame rate strategy
+    // Invalid change frame rate strategy.
     EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT, -1, ""));
     EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT, 2, ""));
 }
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index 32d57b5..5872a47 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -61,7 +61,6 @@
 
 protected:
     void setupScheduler(std::shared_ptr<scheduler::RefreshRateConfigs>);
-    void testChangeRefreshRate(bool isDisplayActive, bool isRefreshRequired);
 
     sp<DisplayDevice> mDisplay;
     mock::EventThread* mAppEventThread;
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
index 6959ee3..9ac2907 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
@@ -107,9 +107,10 @@
 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);
-    EXPECT_EQ(static_cast<bool>(Case::Display::SECURE), device->isSecure());
-    EXPECT_EQ(static_cast<bool>(Case::Display::PRIMARY), device->isPrimary());
+    const auto& display = getDisplayDevice(displayToken);
+
+    EXPECT_EQ(static_cast<bool>(Case::Display::SECURE), display.isSecure());
+    EXPECT_EQ(static_cast<bool>(Case::Display::PRIMARY), display.isPrimary());
 
     std::optional<DisplayDeviceState::Physical> expectedPhysical;
     if (const auto connectionType = Case::Display::CONNECTION_TYPE::value) {
@@ -143,10 +144,11 @@
     // SF should have a display token.
     const auto displayId = Case::Display::DISPLAY_ID::get();
     ASSERT_TRUE(PhysicalDisplayId::tryCast(displayId));
-    ASSERT_EQ(mFlinger.mutablePhysicalDisplayTokens().count(displayId), 1);
-    auto& displayToken = mFlinger.mutablePhysicalDisplayTokens()[displayId];
 
-    verifyDisplayIsConnected<Case>(displayToken);
+    const auto displayTokenOpt = mFlinger.mutablePhysicalDisplayTokens().get(displayId);
+    ASSERT_TRUE(displayTokenOpt);
+
+    verifyDisplayIsConnected<Case>(displayTokenOpt->get());
 }
 
 void DisplayTransactionCommitTest::verifyDisplayIsNotConnected(const sp<IBinder>& displayToken) {
@@ -248,9 +250,9 @@
     // 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);
+    ASSERT_FALSE(mFlinger.mutablePhysicalDisplayTokens().contains(displayId));
 
-    // The existing token should have been removed
+    // The existing token should have been removed.
     verifyDisplayIsNotConnected(existing.token());
 }
 
@@ -330,7 +332,7 @@
                 // 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);
+                ASSERT_FALSE(mFlinger.mutablePhysicalDisplayTokens().contains(displayId));
             }(),
             testing::KilledBySignal(SIGABRT), "Primary display cannot be disconnected.");
 }
@@ -369,15 +371,16 @@
                 // --------------------------------------------------------------------
                 // Postconditions
 
-                // The existing token should have been removed
+                // 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
+                const auto displayTokenOpt = mFlinger.mutablePhysicalDisplayTokens().get(displayId);
+                ASSERT_TRUE(displayTokenOpt);
+                EXPECT_NE(existing.token(), displayTokenOpt->get());
 
+                // A new display should be connected in its place.
                 verifyPhysicalDisplayIsConnected<Case>();
 
                 // --------------------------------------------------------------------
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
new file mode 100644
index 0000000..8de9e4b
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
@@ -0,0 +1,154 @@
+/*
+ * 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 "SurfaceFlingerPowerHintTest"
+
+#include <compositionengine/Display.h>
+#include <compositionengine/mock/DisplaySurface.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <renderengine/mock/RenderEngine.h>
+#include <algorithm>
+#include <chrono>
+#include <memory>
+#include "TestableSurfaceFlinger.h"
+#include "mock/DisplayHardware/MockComposer.h"
+#include "mock/DisplayHardware/MockPowerAdvisor.h"
+#include "mock/MockEventThread.h"
+#include "mock/MockTimeStats.h"
+#include "mock/MockVsyncController.h"
+#include "mock/system/window/MockNativeWindow.h"
+
+using namespace android;
+using namespace android::Hwc2::mock;
+using namespace android::hardware::power;
+using namespace std::chrono_literals;
+using namespace testing;
+
+namespace android {
+namespace {
+using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
+
+constexpr hal::HWDisplayId HWC_DISPLAY = FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID;
+constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u);
+constexpr int DEFAULT_DISPLAY_WIDTH = 1920;
+constexpr int DEFAULT_DISPLAY_HEIGHT = 1024;
+
+class SurfaceFlingerPowerHintTest : public Test {
+public:
+    void SetUp() override;
+
+    void setupScheduler();
+
+protected:
+    TestableSurfaceFlinger mFlinger;
+    renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
+    sp<DisplayDevice> mDisplay;
+    sp<compositionengine::mock::DisplaySurface> mDisplaySurface =
+            new compositionengine::mock::DisplaySurface();
+    mock::NativeWindow* mNativeWindow = new mock::NativeWindow();
+    mock::TimeStats* mTimeStats = new mock::TimeStats();
+    Hwc2::mock::PowerAdvisor* mPowerAdvisor = nullptr;
+    Hwc2::mock::Composer* mComposer = nullptr;
+};
+
+void SurfaceFlingerPowerHintTest::SetUp() {
+    setupScheduler();
+    mComposer = new Hwc2::mock::Composer();
+    mPowerAdvisor = new Hwc2::mock::PowerAdvisor();
+    mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
+    mFlinger.setupTimeStats(std::shared_ptr<TimeStats>(mTimeStats));
+    mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer));
+    mFlinger.setupPowerAdvisor(std::unique_ptr<Hwc2::PowerAdvisor>(mPowerAdvisor));
+    static constexpr bool kIsPrimary = true;
+    FakeHwcDisplayInjector(DEFAULT_DISPLAY_ID, hal::DisplayType::PHYSICAL, kIsPrimary)
+            .setPowerMode(hal::PowerMode::ON)
+            .inject(&mFlinger, mComposer);
+    auto compostionEngineDisplayArgs =
+            compositionengine::DisplayCreationArgsBuilder()
+                    .setId(DEFAULT_DISPLAY_ID)
+                    .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT})
+                    .setPowerAdvisor(mPowerAdvisor)
+                    .setName("injected display")
+                    .build();
+    auto compositionDisplay =
+            compositionengine::impl::createDisplay(mFlinger.getCompositionEngine(),
+                                                   std::move(compostionEngineDisplayArgs));
+    mDisplay =
+            FakeDisplayDeviceInjector(mFlinger, compositionDisplay,
+                                      ui::DisplayConnectionType::Internal, HWC_DISPLAY, kIsPrimary)
+                    .setDisplaySurface(mDisplaySurface)
+                    .setNativeWindow(mNativeWindow)
+                    .setPowerMode(hal::PowerMode::ON)
+                    .inject();
+}
+
+void SurfaceFlingerPowerHintTest::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);
+}
+
+TEST_F(SurfaceFlingerPowerHintTest, sendDurationsIncludingHwcWaitTime) {
+    ON_CALL(*mPowerAdvisor, usePowerHintSession()).WillByDefault(Return(true));
+
+    const std::chrono::nanoseconds mockVsyncPeriod = 15ms;
+    EXPECT_CALL(*mPowerAdvisor, setTargetWorkDuration(_)).Times(1);
+
+    const nsecs_t now = systemTime();
+    const std::chrono::nanoseconds mockHwcRunTime = 20ms;
+    EXPECT_CALL(*mDisplaySurface,
+                prepareFrame(compositionengine::DisplaySurface::CompositionType::Hwc))
+            .Times(1);
+    EXPECT_CALL(*mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _))
+            .WillOnce([mockHwcRunTime] {
+                std::this_thread::sleep_for(mockHwcRunTime);
+                return hardware::graphics::composer::V2_1::Error::NONE;
+            });
+    EXPECT_CALL(*mPowerAdvisor,
+                sendActualWorkDuration(Gt(mockHwcRunTime.count()),
+                                       Gt(now + mockHwcRunTime.count())))
+            .Times(1);
+    static constexpr bool kVsyncId = 123; // arbitrary
+    mFlinger.commitAndComposite(now, kVsyncId, now + mockVsyncPeriod.count());
+}
+
+} // namespace
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
index 7948e60..583cf5f 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
@@ -406,7 +406,7 @@
     display.inject();
 
     // The display is set to PowerMode::ON
-    getDisplayDevice(display.token())->setPowerMode(PowerMode::ON);
+    display.mutableDisplayDevice()->setPowerMode(PowerMode::ON);
 
     // --------------------------------------------------------------------
     // Invocation
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index bf2465f..f1a69fb 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <algorithm>
+#include <chrono>
 #include <variant>
 
 #include <compositionengine/Display.h>
@@ -200,6 +201,10 @@
                 std::make_unique<impl::HWComposer>(std::move(composer)));
     }
 
+    void setupPowerAdvisor(std::unique_ptr<Hwc2::PowerAdvisor> powerAdvisor) {
+        mFlinger->mPowerAdvisor = std::move(powerAdvisor);
+    }
+
     void setupTimeStats(const std::shared_ptr<TimeStats>& timeStats) {
         mFlinger->mCompositionEngine->setTimeStats(timeStats);
     }
@@ -328,11 +333,26 @@
     /* ------------------------------------------------------------------------
      * Forwarding for functions being tested
      */
+
+    nsecs_t commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expectedVSyncTime) {
+        mFlinger->commit(frameTime, vsyncId, expectedVSyncTime);
+        return frameTime;
+    }
+
+    nsecs_t commit(nsecs_t frameTime, int64_t vsyncId) {
+        std::chrono::nanoseconds period = 10ms;
+        return commit(frameTime, vsyncId, frameTime + period.count());
+    }
+
     nsecs_t commit() {
         const nsecs_t now = systemTime();
         const nsecs_t expectedVsyncTime = now + 10'000'000;
-        mFlinger->commit(now, kVsyncId, expectedVsyncTime);
-        return now;
+        return commit(now, kVsyncId, expectedVsyncTime);
+    }
+
+    void commitAndComposite(const nsecs_t frameTime, const int64_t vsyncId,
+                            const nsecs_t expectedVsyncTime) {
+        mFlinger->composite(commit(frameTime, vsyncId, expectedVsyncTime), kVsyncId);
     }
 
     void commitAndComposite() { mFlinger->composite(commit(), kVsyncId); }
@@ -458,11 +478,6 @@
         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) {
@@ -502,6 +517,12 @@
      * post-conditions.
      */
 
+    const auto& displays() const { return mFlinger->mDisplays; }
+    const auto& currentState() const { return mFlinger->mCurrentState; }
+    const auto& drawingState() const { return mFlinger->mDrawingState; }
+    const auto& transactionFlags() const { return mFlinger->mTransactionFlags; }
+    const auto& hwcPhysicalDisplayIdMap() const { return getHwComposer().mPhysicalDisplayIdMap; }
+
     auto& mutableHasWideColorDisplay() { return SurfaceFlinger::hasWideColorDisplay; }
 
     auto& mutableCurrentState() { return mFlinger->mCurrentState; }
@@ -515,7 +536,6 @@
     auto& mutablePhysicalDisplayTokens() { return mFlinger->mPhysicalDisplayTokens; }
     auto& mutableTexturePool() { return mFlinger->mTexturePool; }
     auto& mutableTransactionFlags() { return mFlinger->mTransactionFlags; }
-    auto& mutablePowerAdvisor() { return mFlinger->mPowerAdvisor; }
     auto& mutableDebugDisableHWC() { return mFlinger->mDebugDisableHWC; }
     auto& mutableMaxRenderTargetSize() { return mFlinger->mMaxRenderTargetSize; }
 
@@ -741,7 +761,9 @@
             return mFlinger.mutableCurrentState().displays.valueFor(mDisplayToken);
         }
 
-        auto& mutableDisplayDevice() { return mFlinger.mutableDisplays()[mDisplayToken]; }
+        const sp<DisplayDevice>& mutableDisplayDevice() {
+            return mFlinger.mutableDisplays().get(mDisplayToken)->get();
+        }
 
         // If `configs` is nullptr, the injector creates RefreshRateConfigs from the `modes`.
         // Otherwise, it uses `configs`, which the caller must create using the same `modes`.
@@ -848,12 +870,14 @@
             if (!display->isVirtual()) {
                 display->setActiveMode(activeModeId);
             }
-            mFlinger.mutableDisplays().emplace(mDisplayToken, display);
+            mFlinger.mutableDisplays().emplace_or_replace(mDisplayToken, display);
+
             mFlinger.mutableCurrentState().displays.add(mDisplayToken, state);
             mFlinger.mutableDrawingState().displays.add(mDisplayToken, state);
 
             if (const auto& physical = state.physical) {
-                mFlinger.mutablePhysicalDisplayTokens()[physical->id] = mDisplayToken;
+                mFlinger.mutablePhysicalDisplayTokens().emplace_or_replace(physical->id,
+                                                                           mDisplayToken);
             }
 
             return display;
diff --git a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
index 0ef8456..6ffc039 100644
--- a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
@@ -268,8 +268,11 @@
     for (size_t i = 0; i < MISSED_FRAMES; i++) {
         ASSERT_NO_FATAL_FAILURE(mTimeStats->incrementMissedFrames());
     }
+    TimeStats::ClientCompositionRecord record;
+    record.hadClientComposition = true;
+
     for (size_t i = 0; i < CLIENT_COMPOSITION_FRAMES; i++) {
-        ASSERT_NO_FATAL_FAILURE(mTimeStats->incrementClientCompositionFrames());
+        ASSERT_NO_FATAL_FAILURE(mTimeStats->pushCompositionStrategyState(record));
     }
 
     SFTimeStatsGlobalProto globalProto;
@@ -459,19 +462,49 @@
     EXPECT_THAT(result, HasSubstr(expectedResult));
 }
 
-TEST_F(TimeStatsTest, canIncreaseClientCompositionReusedFrames) {
+TEST_F(TimeStatsTest, canIncreaseClientCompositionStats) {
     // this stat is not in the proto so verify by checking the string dump
-    constexpr size_t CLIENT_COMPOSITION_REUSED_FRAMES = 2;
+    constexpr size_t COMPOSITION_STRATEGY_CHANGED_FRAMES = 1;
+    constexpr size_t HAD_CLIENT_COMPOSITION_FRAMES = 2;
+    constexpr size_t REUSED_CLIENT_COMPOSITION_FRAMES = 3;
+    constexpr size_t COMPOSITION_STRATEGY_PREDICTION_SUCCEEDED_FRAMES = 4;
+    constexpr size_t COMPOSITION_STRATEGY_PREDICTED_FRAMES = 5;
 
     EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
-    for (size_t i = 0; i < CLIENT_COMPOSITION_REUSED_FRAMES; i++) {
-        ASSERT_NO_FATAL_FAILURE(mTimeStats->incrementClientCompositionReusedFrames());
+    for (size_t i = 0; i <= COMPOSITION_STRATEGY_PREDICTED_FRAMES; i++) {
+        TimeStats::ClientCompositionRecord record;
+        record.hadClientComposition = i < HAD_CLIENT_COMPOSITION_FRAMES;
+        record.changed = i < COMPOSITION_STRATEGY_CHANGED_FRAMES;
+        record.reused = i < REUSED_CLIENT_COMPOSITION_FRAMES;
+        record.predicted = i < COMPOSITION_STRATEGY_PREDICTED_FRAMES;
+        record.predictionSucceeded = i < COMPOSITION_STRATEGY_PREDICTION_SUCCEEDED_FRAMES;
+        mTimeStats->pushCompositionStrategyState(record);
     }
 
     const std::string result(inputCommand(InputCommand::DUMP_ALL, FMT_STRING));
-    const std::string expectedResult =
-            "clientCompositionReusedFrames = " + std::to_string(CLIENT_COMPOSITION_REUSED_FRAMES);
-    EXPECT_THAT(result, HasSubstr(expectedResult));
+    std::string expected =
+            "compositionStrategyChanges = " + std::to_string(COMPOSITION_STRATEGY_CHANGED_FRAMES);
+    EXPECT_THAT(result, HasSubstr(expected));
+
+    expected = "clientCompositionFrames = " + std::to_string(HAD_CLIENT_COMPOSITION_FRAMES);
+    EXPECT_THAT(result, HasSubstr(expected));
+
+    expected =
+            "clientCompositionReusedFrames = " + std::to_string(REUSED_CLIENT_COMPOSITION_FRAMES);
+    EXPECT_THAT(result, HasSubstr(expected));
+
+    expected = "compositionStrategyPredicted = " +
+            std::to_string(COMPOSITION_STRATEGY_PREDICTED_FRAMES);
+    EXPECT_THAT(result, HasSubstr(expected));
+
+    expected = "compositionStrategyPredictionSucceeded = " +
+            std::to_string(COMPOSITION_STRATEGY_PREDICTION_SUCCEEDED_FRAMES);
+    EXPECT_THAT(result, HasSubstr(expected));
+
+    expected = "compositionStrategyPredictionFailed = " +
+            std::to_string(COMPOSITION_STRATEGY_PREDICTED_FRAMES -
+                           COMPOSITION_STRATEGY_PREDICTION_SUCCEEDED_FRAMES);
+    EXPECT_THAT(result, HasSubstr(expected));
 }
 
 TEST_F(TimeStatsTest, canIncreaseRefreshRateSwitches) {
@@ -489,21 +522,6 @@
     EXPECT_THAT(result, HasSubstr(expectedResult));
 }
 
-TEST_F(TimeStatsTest, canIncreaseCompositionStrategyChanges) {
-    // this stat is not in the proto so verify by checking the string dump
-    constexpr size_t COMPOSITION_STRATEGY_CHANGES = 2;
-
-    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
-    for (size_t i = 0; i < COMPOSITION_STRATEGY_CHANGES; i++) {
-        ASSERT_NO_FATAL_FAILURE(mTimeStats->incrementCompositionStrategyChanges());
-    }
-
-    const std::string result(inputCommand(InputCommand::DUMP_ALL, FMT_STRING));
-    const std::string expectedResult =
-            "compositionStrategyChanges = " + std::to_string(COMPOSITION_STRATEGY_CHANGES);
-    EXPECT_THAT(result, HasSubstr(expectedResult));
-}
-
 TEST_F(TimeStatsTest, canAverageFrameDuration) {
     EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
     mTimeStats->setPowerMode(PowerMode::ON);
@@ -836,7 +854,7 @@
 
     ASSERT_NO_FATAL_FAILURE(mTimeStats->incrementTotalFrames());
     ASSERT_NO_FATAL_FAILURE(mTimeStats->incrementMissedFrames());
-    ASSERT_NO_FATAL_FAILURE(mTimeStats->incrementClientCompositionFrames());
+    ASSERT_NO_FATAL_FAILURE(mTimeStats->pushCompositionStrategyState({}));
     ASSERT_NO_FATAL_FAILURE(mTimeStats->setPowerMode(PowerMode::ON));
 
     mTimeStats->recordFrameDuration(std::chrono::nanoseconds(3ms).count(),
@@ -867,9 +885,8 @@
 TEST_F(TimeStatsTest, canClearDumpOnlyTimeStats) {
     // These stats are not in the proto so verify by checking the string dump.
     EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
-    ASSERT_NO_FATAL_FAILURE(mTimeStats->incrementClientCompositionReusedFrames());
+    ASSERT_NO_FATAL_FAILURE(mTimeStats->pushCompositionStrategyState({}));
     ASSERT_NO_FATAL_FAILURE(mTimeStats->incrementRefreshRateSwitches());
-    ASSERT_NO_FATAL_FAILURE(mTimeStats->incrementCompositionStrategyChanges());
     mTimeStats->setPowerMode(PowerMode::ON);
     mTimeStats->recordFrameDuration(std::chrono::nanoseconds(1ms).count(),
                                     std::chrono::nanoseconds(5ms).count());
@@ -1032,8 +1049,10 @@
     for (size_t i = 0; i < MISSED_FRAMES; i++) {
         mTimeStats->incrementMissedFrames();
     }
+    TimeStats::ClientCompositionRecord record;
+    record.hadClientComposition = true;
     for (size_t i = 0; i < CLIENT_COMPOSITION_FRAMES; i++) {
-        mTimeStats->incrementClientCompositionFrames();
+        mTimeStats->pushCompositionStrategyState(record);
     }
 
     insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index c1d41bb..aa8b521 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -126,7 +126,8 @@
                  Error(Display, uint64_t, uint64_t, DisplayedFrameStats*));
     MOCK_METHOD3(setLayerPerFrameMetadataBlobs,
                  Error(Display, Layer, const std::vector<IComposerClient::PerFrameMetadataBlob>&));
-    MOCK_METHOD3(setDisplayBrightness, Error(Display, float, const DisplayBrightnessOptions&));
+    MOCK_METHOD4(setDisplayBrightness,
+                 Error(Display, float, float, const DisplayBrightnessOptions&));
     MOCK_METHOD2(
             getDisplayCapabilities,
             Error(Display,
@@ -151,8 +152,10 @@
                              const std::vector<uint8_t>&));
     MOCK_METHOD1(getLayerGenericMetadataKeys,
                  V2_4::Error(std::vector<IComposerClient::LayerGenericMetadataKey>*));
-    MOCK_METHOD3(getClientTargetProperty,
-                 Error(Display, IComposerClient::ClientTargetProperty*, float*));
+    MOCK_METHOD2(getClientTargetProperty,
+                 Error(Display,
+                       aidl::android::hardware::graphics::composer3::
+                               ClientTargetPropertyWithBrightness*));
     MOCK_METHOD3(setLayerBrightness, Error(Display, Layer, float));
     MOCK_METHOD3(setLayerBlockingRegion,
                  Error(Display, Layer, const std::vector<IComposerClient::Rect>&));
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
index ac2ab199c..3e0d6d3 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
@@ -81,7 +81,7 @@
                 (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));
+                (float, float, const Hwc2::Composer::DisplayBrightnessOptions &), (override));
     MOCK_METHOD(hal::Error, setActiveConfigWithConstraints,
                 (hal::HWConfigId, const hal::VsyncPeriodChangeConstraints &,
                  hal::VsyncPeriodChangeTimeline *),
@@ -93,8 +93,10 @@
     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 *, float *),
-                (override));
+    MOCK_METHOD(
+            hal::Error, getClientTargetProperty,
+            (aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness *),
+            (override));
     MOCK_METHOD(
             hal::Error, getDisplayDecorationSupport,
             (std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport> *),
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.cpp b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.cpp
new file mode 100644
index 0000000..2323ebb
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.cpp
@@ -0,0 +1,24 @@
+/*
+ * 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 "mock/DisplayHardware/MockIPower.h"
+
+namespace android::Hwc2::mock {
+
+// Explicit default instantiation is recommended.
+MockIPower::MockIPower() = default;
+
+} // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h
new file mode 100644
index 0000000..0ddc90d
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "binder/Status.h"
+
+#include <android/hardware/power/IPower.h>
+#include <gmock/gmock.h>
+
+using android::binder::Status;
+using android::hardware::power::Boost;
+using android::hardware::power::IPower;
+using android::hardware::power::IPowerHintSession;
+using android::hardware::power::Mode;
+
+namespace android::Hwc2::mock {
+
+class MockIPower : public IPower {
+public:
+    MockIPower();
+
+    MOCK_METHOD(Status, isBoostSupported, (Boost boost, bool* ret), (override));
+    MOCK_METHOD(Status, setBoost, (Boost boost, int32_t durationMs), (override));
+    MOCK_METHOD(Status, isModeSupported, (Mode mode, bool* ret), (override));
+    MOCK_METHOD(Status, setMode, (Mode mode, bool enabled), (override));
+    MOCK_METHOD(Status, createHintSession,
+                (int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
+                 int64_t durationNanos, sp<IPowerHintSession>* session),
+                (override));
+    MOCK_METHOD(Status, getHintSessionPreferredRate, (int64_t * rate), (override));
+    MOCK_METHOD(int32_t, getInterfaceVersion, (), (override));
+    MOCK_METHOD(std::string, getInterfaceHash, (), (override));
+    MOCK_METHOD(IBinder*, onAsBinder, (), (override));
+};
+
+} // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.cpp b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.cpp
new file mode 100644
index 0000000..770bc15
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.cpp
@@ -0,0 +1,24 @@
+/*
+ * 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 "mock/DisplayHardware/MockIPowerHintSession.h"
+
+namespace android::Hwc2::mock {
+
+// Explicit default instantiation is recommended.
+MockIPowerHintSession::MockIPowerHintSession() = default;
+
+} // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
new file mode 100644
index 0000000..439f6f4
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "binder/Status.h"
+
+#include <android/hardware/power/IPower.h>
+#include <gmock/gmock.h>
+
+using android::binder::Status;
+using android::hardware::power::IPowerHintSession;
+
+using namespace android::hardware::power;
+
+namespace android::Hwc2::mock {
+
+class MockIPowerHintSession : public IPowerHintSession {
+public:
+    MockIPowerHintSession();
+
+    MOCK_METHOD(IBinder*, onAsBinder, (), (override));
+    MOCK_METHOD(Status, pause, (), (override));
+    MOCK_METHOD(Status, resume, (), (override));
+    MOCK_METHOD(Status, close, (), (override));
+    MOCK_METHOD(int32_t, getInterfaceVersion, (), (override));
+    MOCK_METHOD(std::string, getInterfaceHash, (), (override));
+    MOCK_METHOD(Status, updateTargetWorkDuration, (int64_t), (override));
+    MOCK_METHOD(Status, reportActualWorkDuration, (const ::std::vector<WorkDuration>&), (override));
+};
+
+} // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h b/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
index 0a69b56..0dee800 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
@@ -33,10 +33,7 @@
     MOCK_METHOD0(miniDump, std::string());
     MOCK_METHOD0(incrementTotalFrames, void());
     MOCK_METHOD0(incrementMissedFrames, void());
-    MOCK_METHOD0(incrementClientCompositionFrames, void());
-    MOCK_METHOD0(incrementClientCompositionReusedFrames, void());
     MOCK_METHOD0(incrementRefreshRateSwitches, void());
-    MOCK_METHOD0(incrementCompositionStrategyChanges, void());
     MOCK_METHOD1(recordDisplayEventConnectionCount, void(int32_t));
     MOCK_METHOD2(recordFrameDuration, void(nsecs_t, nsecs_t));
     MOCK_METHOD2(recordRenderEngineDuration, void(nsecs_t, nsecs_t));
@@ -63,6 +60,8 @@
                  void(hardware::graphics::composer::V2_4::IComposerClient::PowerMode));
     MOCK_METHOD2(recordRefreshRate, void(uint32_t, nsecs_t));
     MOCK_METHOD1(setPresentFenceGlobal, void(const std::shared_ptr<FenceTime>&));
+    MOCK_METHOD(void, pushCompositionStrategyState,
+                (const android::TimeStats::ClientCompositionRecord&), (override));
 };
 
 } // namespace android::mock
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 45bc4c9..c4b1487 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -299,6 +299,7 @@
 }
 
 void ReleaseSwapchainImage(VkDevice device,
+                           bool shared_present,
                            ANativeWindow* window,
                            int release_fence,
                            Swapchain::Image& image,
@@ -330,7 +331,8 @@
         }
         image.dequeue_fence = -1;
 
-        if (window) {
+        // It's invalid to call cancelBuffer on a shared buffer
+        if (window && !shared_present) {
             window->cancelBuffer(window, image.buffer.get(), release_fence);
         } else {
             if (release_fence >= 0) {
@@ -364,9 +366,10 @@
     if (swapchain->surface.swapchain_handle != HandleFromSwapchain(swapchain))
         return;
     for (uint32_t i = 0; i < swapchain->num_images; i++) {
-        if (!swapchain->images[i].dequeued)
-            ReleaseSwapchainImage(device, nullptr, -1, swapchain->images[i],
-                                  true);
+        if (!swapchain->images[i].dequeued) {
+            ReleaseSwapchainImage(device, swapchain->shared, nullptr, -1,
+                                  swapchain->images[i], true);
+        }
     }
     swapchain->surface.swapchain_handle = VK_NULL_HANDLE;
     swapchain->timing.clear();
@@ -1084,7 +1087,8 @@
     }
 
     for (uint32_t i = 0; i < swapchain->num_images; i++) {
-        ReleaseSwapchainImage(device, window, -1, swapchain->images[i], false);
+        ReleaseSwapchainImage(device, swapchain->shared, window, -1,
+                              swapchain->images[i], false);
     }
 
     if (active) {
@@ -1854,7 +1858,8 @@
                     WorstPresentResult(swapchain_result, VK_SUBOPTIMAL_KHR);
             }
         } else {
-            ReleaseSwapchainImage(device, nullptr, fence, img, true);
+            ReleaseSwapchainImage(device, swapchain.shared, nullptr, fence,
+                                  img, true);
             swapchain_result = VK_ERROR_OUT_OF_DATE_KHR;
         }