Merge "SF: trace display power state" into udc-dev
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index c71c4a0..097bc53 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -14,6 +14,7 @@
                include/powermanager/
                libs/binder/fuzzer/
                libs/binder/
+               libs/binderdebug/
                libs/binderthreadstate/
                libs/graphicsenv/
                libs/gui/
@@ -40,3 +41,4 @@
 dumpsys_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "^cmds/dumpsys/"
 # bugreports matches both cmds/bugreport and cmds/bugreportz
 bugreports_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "^cmds/bugreport"
+binder_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "^libs/binder/"
diff --git a/cmds/atrace/atrace.rc b/cmds/atrace/atrace.rc
index 69ca793..e66cc41 100644
--- a/cmds/atrace/atrace.rc
+++ b/cmds/atrace/atrace.rc
@@ -317,12 +317,6 @@
     write /sys/kernel/debug/tracing/tracing_on 1
     write /sys/kernel/tracing/tracing_on 1
 
-on late-init && property:ro.boot.fastboot.boottrace=enabled
-    setprop debug.atrace.tags.enableflags 802922
-    setprop persist.traced.enable 0
-    write /sys/kernel/debug/tracing/tracing_on 1
-    write /sys/kernel/tracing/tracing_on 1
-
 # Only create the tracing instance if persist.mm_events.enabled
 # Attempting to remove the tracing instance after it has been created
 # will likely fail with EBUSY as it would be in use by traced_probes.
@@ -411,6 +405,130 @@
     chmod 0666 /sys/kernel/debug/tracing/instances/mm_events/per_cpu/cpu23/trace
     chmod 0666 /sys/kernel/tracing/instances/mm_events/per_cpu/cpu23/trace
 
+# Handle hyp tracing instance
+on late-init && property:ro.boot.hypervisor.vm.supported=1
+
+# Hypervisor tracing instance doesn't support changing trace_clock
+    chmod 0440 /sys/kernel/debug/tracing/hyp/trace_clock
+    chmod 0440 /sys/kernel/tracing/hyp/trace_clock
+
+    chmod 0660 /sys/kernel/debug/tracing/hyp/buffer_size_kb
+    chmod 0660 /sys/kernel/tracing/hyp/buffer_size_kb
+
+    chmod 0660 /sys/kernel/debug/tracing/hyp/tracing_on
+    chmod 0660 /sys/kernel/tracing/hyp/tracing_on
+
+# Tracing disabled by default
+    write /sys/kernel/debug/tracing/hyp/tracing_on 0
+    write /sys/kernel/tracing/hyp/tracing_on 0
+
+# Read and truncate the hyp trace.
+    chmod 0660 /sys/kernel/debug/tracing/hyp/trace
+    chmod 0660 /sys/kernel/tracing/hyp/trace
+
+# Read and truncate the per-CPU kernel trace.
+# Cannot use wildcards in .rc files. Update this if there is a phone with
+# TODO(b/249050813, ioffe): introduce per-cpu wildcard
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu0/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu0/trace
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu1/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu1/trace
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu2/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu2/trace
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu3/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu3/trace
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu4/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu4/trace
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu5/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu5/trace
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu6/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu6/trace
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu7/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu7/trace
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu8/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu8/trace
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu9/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu9/trace
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu10/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu10/trace
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu11/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu11/trace
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu12/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu12/trace
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu13/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu13/trace
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu14/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu14/trace
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu15/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu15/trace
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu16/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu16/trace
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu17/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu17/trace
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu18/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu18/trace
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu19/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu19/trace
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu20/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu20/trace
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu21/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu21/trace
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu22/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu22/trace
+    chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu23/trace
+    chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu23/trace
+
+    chmod 0440 /sys/kernel/debug/tracing/hyp/events/header_page
+    chmod 0440 /sys/kernel/tracing/hyp/events/header_page
+
+# Hyp events start here
+
+# hyp_enter event
+    chmod 0660 /sys/kernel/debug/tracing/hyp/events/hyp/hyp_enter/enable
+    chmod 0660 /sys/kernel/tracing/hyp/events/hyp/hyp_enter/enable
+# TODO(b/249050813): should this be handled in kernel?
+    chmod 0440 /sys/kernel/debug/tracing/hyp/events/hyp/hyp_enter/format
+    chmod 0440 /sys/kernel/tracing/hyp/events/hyp/hyp_enter/format
+    chmod 0440 /sys/kernel/debug/tracing/hyp/events/hyp/hyp_enter/id
+    chmod 0440 /sys/kernel/tracing/hyp/events/hyp/hyp_enter/id
+
+# hyp_exit event
+    chmod 0660 /sys/kernel/debug/tracing/hyp/events/hyp/hyp_exit/enable
+    chmod 0660 /sys/kernel/tracing/hyp/events/hyp/hyp_exit/enable
+# TODO(b/249050813): should this be handled in kernel?
+    chmod 0440 /sys/kernel/debug/tracing/hyp/events/hyp/hyp_exit/format
+    chmod 0440 /sys/kernel/tracing/hyp/events/hyp/hyp_exit/format
+    chmod 0440 /sys/kernel/debug/tracing/hyp/events/hyp/hyp_exit/id
+    chmod 0440 /sys/kernel/tracing/hyp/events/hyp/hyp_exit/id
+
+# host_hcall event
+    chmod 0660 /sys/kernel/debug/tracing/hyp/events/hyp/host_hcall/enable
+    chmod 0660 /sys/kernel/tracing/hyp/events/hyp/host_hcall/enable
+# TODO(b/249050813): should this be handled in kernel?
+    chmod 0440 /sys/kernel/debug/tracing/hyp/events/hyp/host_hcall/format
+    chmod 0440 /sys/kernel/tracing/hyp/events/hyp/host_hcall/format
+    chmod 0440 /sys/kernel/debug/tracing/hyp/events/hyp/host_hcall/id
+    chmod 0440 /sys/kernel/tracing/hyp/events/hyp/host_hcall/id
+
+# host_smc event
+    chmod 0660 /sys/kernel/debug/tracing/hyp/events/hyp/host_smc/enable
+    chmod 0660 /sys/kernel/tracing/hyp/events/hyp/host_smc/enable
+# TODO(b/249050813): should this be handled in kernel?
+    chmod 0440 /sys/kernel/debug/tracing/hyp/events/hyp/host_smc/format
+    chmod 0440 /sys/kernel/tracing/hyp/events/hyp/host_smc/format
+    chmod 0440 /sys/kernel/debug/tracing/hyp/events/hyp/host_smc/id
+    chmod 0440 /sys/kernel/tracing/hyp/events/hyp/host_smc/id
+
+# host_mem_abort event
+    chmod 0660 /sys/kernel/debug/tracing/hyp/events/hyp/host_mem_abort/enable
+    chmod 0660 /sys/kernel/tracing/hyp/events/hyp/host_mem_abort/enable
+# TODO(b/249050813): should this be handled in kernel?
+    chmod 0440 /sys/kernel/debug/tracing/hyp/events/hyp/host_mem_abort/format
+    chmod 0440 /sys/kernel/tracing/hyp/events/hyp/host_mem_abort/format
+    chmod 0440 /sys/kernel/debug/tracing/hyp/events/hyp/host_mem_abort/id
+    chmod 0440 /sys/kernel/tracing/hyp/events/hyp/host_mem_abort/id
+
+
 on property:persist.debug.atrace.boottrace=1
     start boottrace
 
diff --git a/cmds/dumpstate/OWNERS b/cmds/dumpstate/OWNERS
index 5f56531..ab81ecf 100644
--- a/cmds/dumpstate/OWNERS
+++ b/cmds/dumpstate/OWNERS
@@ -3,3 +3,4 @@
 gavincorkery@google.com
 nandana@google.com
 jsharkey@android.com
+smoreland@google.com
\ No newline at end of file
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 23cdd10..6f84e7c 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -1254,8 +1254,9 @@
              dumpsys.writeDumpHeader(STDOUT_FILENO, service, priority);
              dumpsys.writeDumpFooter(STDOUT_FILENO, service, std::chrono::milliseconds(1));
         } else {
-            status_t status = dumpsys.startDumpThread(Dumpsys::TYPE_DUMP, service, args);
-            if (status == OK) {
+             status_t status = dumpsys.startDumpThread(Dumpsys::TYPE_DUMP | Dumpsys::TYPE_PID,
+                                                       service, args);
+             if (status == OK) {
                 dumpsys.writeDumpHeader(STDOUT_FILENO, service, priority);
                 std::chrono::duration<double> elapsed_seconds;
                 if (priority == IServiceManager::DUMP_FLAG_PRIORITY_HIGH &&
@@ -1902,6 +1903,9 @@
     }
     ds.AddDir(PREREBOOT_DATA_DIR, false);
     add_mountinfo();
+    for (const char* path : {"/proc/cpuinfo", "/proc/meminfo"}) {
+        ds.AddZipEntry(ZIP_ROOT_DIR + path, path);
+    }
     DumpIpTablesAsRoot();
     DumpDynamicPartitionInfo();
     ds.AddDir(OTA_METADATA_DIR, true);
diff --git a/cmds/installd/dexopt.cpp b/cmds/installd/dexopt.cpp
index 34ea759..1693ed5 100644
--- a/cmds/installd/dexopt.cpp
+++ b/cmds/installd/dexopt.cpp
@@ -442,6 +442,16 @@
 static unique_fd open_reference_profile(uid_t uid, const std::string& package_name,
         const std::string& location, bool read_write, bool is_secondary_dex) {
     std::string profile = create_reference_profile_path(package_name, location, is_secondary_dex);
+    if (read_write && GetBoolProperty("dalvik.vm.useartservice", false)) {
+        // ART Service doesn't use flock and instead assumes profile files are
+        // immutable, so ensure we don't open a file for writing when it's
+        // active.
+        // TODO(b/251921228): Normally installd isn't called at all in that
+        // case, but OTA is still an exception that uses the legacy code.
+        LOG(ERROR) << "Opening ref profile " << profile
+                   << " for writing is unsafe when ART Service is enabled.";
+        return invalid_unique_fd();
+    }
     return open_profile(
         uid,
         profile,
@@ -450,14 +460,13 @@
 }
 
 static UniqueFile open_reference_profile_as_unique_file(uid_t uid, const std::string& package_name,
-        const std::string& location, bool read_write, bool is_secondary_dex) {
+                                                        const std::string& location,
+                                                        bool is_secondary_dex) {
     std::string profile_path = create_reference_profile_path(package_name, location,
                                                              is_secondary_dex);
-    unique_fd ufd = open_profile(
-        uid,
-        profile_path,
-        read_write ? (O_CREAT | O_RDWR) : O_RDONLY,
-        S_IRUSR | S_IWUSR | S_IRGRP);  // so that ART can also read it when apps run.
+    unique_fd ufd = open_profile(uid, profile_path, O_RDONLY,
+                                 S_IRUSR | S_IWUSR |
+                                         S_IRGRP); // so that ART can also read it when apps run.
 
     return UniqueFile(ufd.release(), profile_path, [](const std::string& path) {
         clear_profile(path);
@@ -1104,8 +1113,7 @@
             location = profile_name;
         }
     }
-    return open_reference_profile_as_unique_file(uid, pkgname, location, /*read_write*/false,
-                                                 is_secondary_dex);
+    return open_reference_profile_as_unique_file(uid, pkgname, location, is_secondary_dex);
 }
 
 // Opens the vdex files and assigns the input fd to in_vdex_wrapper and the output fd to
@@ -1909,10 +1917,11 @@
     // Open the reference profile if needed.
     UniqueFile reference_profile = maybe_open_reference_profile(
             pkgname, dex_path, profile_name, profile_guided, is_public, uid, is_secondary_dex);
-
-    if (reference_profile.fd() == -1) {
-        // We don't create an app image without reference profile since there is no speedup from
-        // loading it in that case and instead will be a small overhead.
+    struct stat sbuf;
+    if (reference_profile.fd() == -1 ||
+        (fstat(reference_profile.fd(), &sbuf) != -1 && sbuf.st_size == 0)) {
+        // We don't create an app image with empty or non existing reference profile since there
+        // is no speedup from loading it in that case and instead will be a small overhead.
         generate_app_image = false;
     }
 
@@ -1933,7 +1942,8 @@
         RUNTIME_NATIVE_BOOT_NAMESPACE,
         ENABLE_JITZYGOTE_IMAGE,
         /*default_value=*/ "");
-    bool use_jitzygote_image = jitzygote_flag == "true" || IsBootClassPathProfilingEnable();
+    bool compile_without_image = jitzygote_flag == "true" || IsBootClassPathProfilingEnable() ||
+            force_compile_without_image();
 
     // Decide whether to use dex2oat64.
     bool use_dex2oat64 = false;
@@ -1955,7 +1965,7 @@
                       in_dex, in_vdex, dex_metadata, reference_profile, class_loader_context,
                       join_fds(context_input_fds), swap_fd.get(), instruction_set, compiler_filter,
                       debuggable, boot_complete, for_restore, target_sdk_version,
-                      enable_hidden_api_checks, generate_compact_dex, use_jitzygote_image,
+                      enable_hidden_api_checks, generate_compact_dex, compile_without_image,
                       background_job_compile, compilation_reason);
 
     bool cancelled = false;
diff --git a/cmds/installd/installd.cpp b/cmds/installd/installd.cpp
index b5bc28c..4f691c9 100644
--- a/cmds/installd/installd.cpp
+++ b/cmds/installd/installd.cpp
@@ -65,6 +65,10 @@
     return create_cache_path_default(path, src, instruction_set);
 }
 
+bool force_compile_without_image() {
+    return false;
+}
+
 static bool initialize_globals() {
     return init_globals_from_data_and_root();
 }
diff --git a/cmds/installd/installd_deps.h b/cmds/installd/installd_deps.h
index 5093178..0d0a7fa 100644
--- a/cmds/installd/installd_deps.h
+++ b/cmds/installd/installd_deps.h
@@ -57,6 +57,9 @@
                               const char *src,
                               const char *instruction_set);
 
+// If true, pass "--force-jit-zygote" to dex2oat (i.e., compile without a boot image).
+extern bool force_compile_without_image();
+
 }  // namespace installd
 }  // namespace android
 
diff --git a/cmds/installd/otapreopt.cpp b/cmds/installd/otapreopt.cpp
index 6a3120c..7cabdb0 100644
--- a/cmds/installd/otapreopt.cpp
+++ b/cmds/installd/otapreopt.cpp
@@ -308,7 +308,7 @@
         // This is different from the normal installd. We only do the base
         // directory, the rest will be created on demand when each app is compiled.
         if (access(GetOtaDirectoryPrefix().c_str(), R_OK) < 0) {
-            LOG(ERROR) << "Could not access " << GetOtaDirectoryPrefix();
+            PLOG(ERROR) << "Could not access " << GetOtaDirectoryPrefix();
             return false;
         }
 
@@ -460,7 +460,7 @@
         // this tool will wipe the OTA artifact cache and try again (for robustness after
         // a failed OTA with remaining cache artifacts).
         if (access(apk_path, F_OK) != 0) {
-            LOG(WARNING) << "Skipping A/B OTA preopt of non-existing package " << apk_path;
+            PLOG(WARNING) << "Skipping A/B OTA preopt of non-existing package " << apk_path;
             return true;
         }
 
@@ -711,6 +711,11 @@
     return true;
 }
 
+bool force_compile_without_image() {
+    // We don't have a boot image anyway. Compile without a boot image.
+    return true;
+}
+
 static int log_callback(int type, const char *fmt, ...) {
     va_list ap;
     int priority;
diff --git a/cmds/installd/otapreopt_chroot.cpp b/cmds/installd/otapreopt_chroot.cpp
index c62734a..1b7acab 100644
--- a/cmds/installd/otapreopt_chroot.cpp
+++ b/cmds/installd/otapreopt_chroot.cpp
@@ -45,6 +45,10 @@
 namespace android {
 namespace installd {
 
+// We don't know the filesystem types of the partitions in the update package,
+// so just try the possibilities one by one.
+static constexpr std::array kTryMountFsTypes = {"ext4", "erofs"};
+
 static void CloseDescriptor(int fd) {
     if (fd >= 0) {
         int result = close(fd);
@@ -82,6 +86,27 @@
     }
 }
 
+static bool TryMountWithFstypes(const char* block_device, const char* target) {
+    for (int i = 0; i < kTryMountFsTypes.size(); ++i) {
+        const char* fstype = kTryMountFsTypes[i];
+        int mount_result = mount(block_device, target, fstype, MS_RDONLY, /* data */ nullptr);
+        if (mount_result == 0) {
+            return true;
+        }
+        if (errno == EINVAL && i < kTryMountFsTypes.size() - 1) {
+            // Only try the next fstype if mounting failed due to the current one
+            // being invalid.
+            LOG(WARNING) << "Failed to mount " << block_device << " on " << target << " with "
+                         << fstype << " - trying " << kTryMountFsTypes[i + 1];
+        } else {
+            PLOG(ERROR) << "Failed to mount " << block_device << " on " << target << " with "
+                        << fstype;
+            return false;
+        }
+    }
+    __builtin_unreachable();
+}
+
 static void TryExtraMount(const char* name, const char* slot, const char* target) {
     std::string partition_name = StringPrintf("%s%s", name, slot);
 
@@ -91,12 +116,7 @@
         if (dm.GetState(partition_name) != dm::DmDeviceState::INVALID) {
             std::string path;
             if (dm.GetDmDevicePathByName(partition_name, &path)) {
-                int mount_result = mount(path.c_str(),
-                                         target,
-                                         "ext4",
-                                         MS_RDONLY,
-                                         /* data */ nullptr);
-                if (mount_result == 0) {
+                if (TryMountWithFstypes(path.c_str(), target)) {
                     return;
                 }
             }
@@ -105,12 +125,7 @@
 
     // Fall back and attempt a direct mount.
     std::string block_device = StringPrintf("/dev/block/by-name/%s", partition_name.c_str());
-    int mount_result = mount(block_device.c_str(),
-                             target,
-                             "ext4",
-                             MS_RDONLY,
-                             /* data */ nullptr);
-    UNUSED(mount_result);
+    (void)TryMountWithFstypes(block_device.c_str(), target);
 }
 
 // Entry for otapreopt_chroot. Expected parameters are:
diff --git a/cmds/installd/otapreopt_script.sh b/cmds/installd/otapreopt_script.sh
index f950276..db5c34e 100644
--- a/cmds/installd/otapreopt_script.sh
+++ b/cmds/installd/otapreopt_script.sh
@@ -60,6 +60,11 @@
 
 i=0
 while ((i<MAXIMUM_PACKAGES)) ; do
+  DONE=$(cmd otadexopt done)
+  if [ "$DONE" = "OTA complete." ] ; then
+    break
+  fi
+
   DEXOPT_PARAMS=$(cmd otadexopt next)
 
   /system/bin/otapreopt_chroot $STATUS_FD $TARGET_SLOT_SUFFIX $DEXOPT_PARAMS >&- 2>&-
@@ -67,13 +72,8 @@
   PROGRESS=$(cmd otadexopt progress)
   print -u${STATUS_FD} "global_progress $PROGRESS"
 
-  DONE=$(cmd otadexopt done)
-  if [ "$DONE" = "OTA incomplete." ] ; then
-    sleep 1
-    i=$((i+1))
-    continue
-  fi
-  break
+  sleep 1
+  i=$((i+1))
 done
 
 DONE=$(cmd otadexopt done)
diff --git a/cmds/installd/tests/installd_cache_test.cpp b/cmds/installd/tests/installd_cache_test.cpp
index 4976646..a00c2c7 100644
--- a/cmds/installd/tests/installd_cache_test.cpp
+++ b/cmds/installd/tests/installd_cache_test.cpp
@@ -67,6 +67,10 @@
     return false;
 }
 
+bool force_compile_without_image() {
+    return false;
+}
+
 static void mkdir(const char* path) {
     const std::string fullPath = StringPrintf("/data/local/tmp/user/0/%s", path);
     ::mkdir(fullPath.c_str(), 0755);
diff --git a/cmds/installd/tests/installd_dexopt_test.cpp b/cmds/installd/tests/installd_dexopt_test.cpp
index 3b589dc..be4ca43 100644
--- a/cmds/installd/tests/installd_dexopt_test.cpp
+++ b/cmds/installd/tests/installd_dexopt_test.cpp
@@ -73,6 +73,10 @@
     return create_cache_path_default(path, src, instruction_set);
 }
 
+bool force_compile_without_image() {
+    return false;
+}
+
 static void run_cmd(const std::string& cmd) {
     system(cmd.c_str());
 }
@@ -185,7 +189,7 @@
     std::optional<std::string> volume_uuid_;
     std::string package_name_;
     std::string apk_path_;
-    std::string empty_dm_file_;
+    std::string dm_file_;
     std::string app_apk_dir_;
     std::string app_private_dir_ce_;
     std::string app_private_dir_de_;
@@ -248,26 +252,6 @@
                                                  << " : " << error_msg;
         }
 
-        // Create an empty dm file.
-        empty_dm_file_ = apk_path_ + ".dm";
-        {
-            int fd = open(empty_dm_file_.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
-            if (fd < 0) {
-                return ::testing::AssertionFailure() << "Could not open " << empty_dm_file_;
-            }
-            FILE* file = fdopen(fd, "wb");
-            if (file == nullptr) {
-                return ::testing::AssertionFailure() << "Null file for " << empty_dm_file_
-                         << " fd=" << fd;
-            }
-            ZipWriter writer(file);
-            // Add vdex to zip.
-            writer.StartEntry("primary.prof", ZipWriter::kCompress);
-            writer.FinishEntry();
-            writer.Finish();
-            fclose(file);
-          }
-
         // Create the app user data.
         binder::Status status = service_->createAppData(
                 volume_uuid_,
@@ -316,6 +300,46 @@
                                                  << secondary_dex_de_ << " : " << error_msg;
         }
 
+        // Create a non-empty dm file.
+        dm_file_ = apk_path_ + ".dm";
+        {
+            android::base::unique_fd fd(open(dm_file_.c_str(),
+                                          O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR));
+            if (fd.get() < 0) {
+                return ::testing::AssertionFailure() << "Could not open " << dm_file_;
+            }
+            FILE* file = fdopen(fd.release(), "wb");
+            if (file == nullptr) {
+                return ::testing::AssertionFailure() << "Null file for " << dm_file_
+                                  << " fd=" << fd.get();
+            }
+
+            // Create a profile file.
+            std::string profile_file = app_private_dir_ce_ + "/primary.prof";
+            run_cmd("profman --generate-test-profile=" + profile_file);
+
+            // Add profile to zip.
+            ZipWriter writer(file);
+            writer.StartEntry("primary.prof", ZipWriter::kCompress);
+            android::base::unique_fd profile_fd(open(profile_file.c_str(), O_RDONLY));
+            if (profile_fd.get() < 0) {
+                return ::testing::AssertionFailure() << "Failed to open profile '"
+                                  << profile_file << "'";
+            }
+            std::string profile_content;
+            if (!android::base::ReadFdToString(profile_fd, &profile_content)) {
+                return ::testing::AssertionFailure() << "Failed to read profile "
+                                  << profile_file << "'";
+            }
+            writer.WriteBytes(profile_content.c_str(), profile_content.length());
+            writer.FinishEntry();
+            writer.Finish();
+            fclose(file);
+
+            // Delete the temp file.
+            unlink(profile_file.c_str());
+        }
+
         // Fix app data uid.
         status = service_->fixupAppData(volume_uuid_, kTestUserId);
         if (!status.isOk()) {
@@ -608,7 +632,7 @@
                 kTestAppGid,
                 DEX2OAT_FROM_SCRATCH,
                 /*binder_result=*/nullptr,
-                empty_dm_file_.c_str());
+                dm_file_.c_str());
 
 
         int64_t odex_size = GetSize(GetPrimaryDexArtifact(oat_dir, apk_path_,
@@ -657,13 +681,13 @@
                             DEXOPT_BOOTCOMPLETE | DEXOPT_PROFILE_GUIDED | DEXOPT_PUBLIC |
                                     DEXOPT_GENERATE_APP_IMAGE,
                             oat_dir, kTestAppGid, DEX2OAT_FROM_SCRATCH,
-                            /*binder_result=*/nullptr, empty_dm_file_.c_str());
+                            /*binder_result=*/nullptr, dm_file_.c_str());
         checkVisibility(in_dalvik_cache, ODEX_IS_PUBLIC);
 
         CompilePrimaryDexOk("speed-profile",
                             DEXOPT_BOOTCOMPLETE | DEXOPT_PROFILE_GUIDED | DEXOPT_GENERATE_APP_IMAGE,
                             oat_dir, kTestAppGid, DEX2OAT_FROM_SCRATCH,
-                            /*binder_result=*/nullptr, empty_dm_file_.c_str());
+                            /*binder_result=*/nullptr, dm_file_.c_str());
         checkVisibility(in_dalvik_cache, ODEX_IS_PRIVATE);
     }
 };
@@ -787,7 +811,7 @@
                         kTestAppGid,
                         DEX2OAT_FROM_SCRATCH,
                         /*binder_result=*/nullptr,
-                        empty_dm_file_.c_str());
+                        dm_file_.c_str());
 }
 
 TEST_F(DexoptTest, DexoptPrimaryProfilePublic) {
@@ -799,7 +823,7 @@
                         kTestAppGid,
                         DEX2OAT_FROM_SCRATCH,
                         /*binder_result=*/nullptr,
-                        empty_dm_file_.c_str());
+                        dm_file_.c_str());
 }
 
 TEST_F(DexoptTest, DexoptPrimaryBackgroundOk) {
@@ -811,7 +835,7 @@
                         kTestAppGid,
                         DEX2OAT_FROM_SCRATCH,
                         /*binder_result=*/nullptr,
-                        empty_dm_file_.c_str());
+                        dm_file_.c_str());
 }
 
 TEST_F(DexoptTest, DexoptBlockPrimary) {
@@ -874,7 +898,7 @@
                         kTestAppGid,
                         DEX2OAT_FROM_SCRATCH,
                         /*binder_result=*/nullptr,
-                        empty_dm_file_.c_str());
+                        dm_file_.c_str());
     run_cmd_and_process_output(
             "oatdump --header-only --oat-file=" + odex,
             [&](const std::string& line) {
@@ -893,7 +917,7 @@
                         kTestAppGid,
                         DEX2OAT_FROM_SCRATCH,
                         /*binder_result=*/nullptr,
-                        empty_dm_file_.c_str());
+                        dm_file_.c_str());
     run_cmd_and_process_output(
             "oatdump --header-only --oat-file=" + odex,
             [&](const std::string& line) {
@@ -926,7 +950,7 @@
                         kTestAppGid,
                         DEX2OAT_FROM_SCRATCH,
                         /*binder_result=*/nullptr,
-                        empty_dm_file_.c_str());
+                        dm_file_.c_str());
     // Enable the property and use dex2oat64.
     ASSERT_TRUE(android::base::SetProperty(property, "true")) << property;
     CompilePrimaryDexOk("speed-profile",
@@ -936,7 +960,7 @@
                         kTestAppGid,
                         DEX2OAT_FROM_SCRATCH,
                         /*binder_result=*/nullptr,
-                        empty_dm_file_.c_str());
+                        dm_file_.c_str());
 }
 
 class PrimaryDexReCompilationTest : public DexoptTest {
@@ -1143,7 +1167,7 @@
                 service_->prepareAppProfile(package_name, has_user_id ? kTestUserId : USER_NULL,
                                             kTestAppId, profile_name, apk_path_,
                                             has_dex_metadata ? std::make_optional<std::string>(
-                                                                       empty_dm_file_)
+                                                                       dm_file_)
                                                              : std::nullopt,
                                             &result));
         ASSERT_EQ(expected_result, result);
diff --git a/cmds/installd/tests/installd_service_test.cpp b/cmds/installd/tests/installd_service_test.cpp
index f86f1d5..858a92c 100644
--- a/cmds/installd/tests/installd_service_test.cpp
+++ b/cmds/installd/tests/installd_service_test.cpp
@@ -113,6 +113,10 @@
     return create_cache_path_default(path, src, instruction_set);
 }
 
+bool force_compile_without_image() {
+    return false;
+}
+
 static std::string get_full_path(const std::string& path) {
     return StringPrintf("%s/%s", kTestPath.c_str(), path.c_str());
 }
diff --git a/cmds/service/service.cpp b/cmds/service/service.cpp
index d5ca725..5e8ef5d 100644
--- a/cmds/service/service.cpp
+++ b/cmds/service/service.cpp
@@ -75,7 +75,7 @@
     ProcessState::initWithDriver("/dev/vndbinder");
 #endif
 #ifndef __ANDROID__
-    setDefaultServiceManager(createRpcDelegateServiceManager({.maxOutgoingThreads = 1}));
+    setDefaultServiceManager(createRpcDelegateServiceManager({.maxOutgoingConnections = 1}));
 #endif
     sp<IServiceManager> sm = defaultServiceManager();
     fflush(stdout);
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index 07809e2..4da0cd6 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -228,8 +228,13 @@
 #endif  // !VENDORSERVICEMANAGER
 
 ServiceManager::Service::~Service() {
-    if (!hasClients) {
-        // only expected to happen on process death
+    if (hasClients) {
+        // only expected to happen on process death, we don't store the service
+        // name this late (it's in the map that holds this service), but if it
+        // is happening, we might want to change 'unlinkToDeath' to explicitly
+        // clear this bit so that we can abort in other cases, where it would
+        // mean inconsistent logic in servicemanager (unexpected and tested, but
+        // the original lazy service impl here had that bug).
         LOG(WARNING) << "a service was removed when there are clients";
     }
 }
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index a737bd3..bdd5172 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -167,12 +167,6 @@
 }
 
 prebuilt_etc {
-    name: "android.hardware.telephony.satellite.prebuilt.xml",
-    src: "android.hardware.telephony.satellite.xml",
-    defaults: ["frameworks_native_data_etc_defaults"],
-}
-
-prebuilt_etc {
     name: "android.hardware.usb.accessory.prebuilt.xml",
     src: "android.hardware.usb.accessory.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
diff --git a/data/etc/android.hardware.telephony.satellite.xml b/data/etc/android.hardware.telephony.satellite.xml
deleted file mode 100644
index 5966cba..0000000
--- a/data/etc/android.hardware.telephony.satellite.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2022 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<!-- Feature for devices that support Satellite communication via Satellite HAL APIs. -->
-<permissions>
-    <feature name="android.hardware.telephony.satellite" />
-</permissions>
diff --git a/headers/media_plugin/media/openmax/OMX_AsString.h b/headers/media_plugin/media/openmax/OMX_AsString.h
index ce30b41..165a868 100644
--- a/headers/media_plugin/media/openmax/OMX_AsString.h
+++ b/headers/media_plugin/media/openmax/OMX_AsString.h
@@ -561,6 +561,7 @@
         case OMX_IndexConfigPriority:                   return "ConfigPriority";
         case OMX_IndexConfigOperatingRate:              return "ConfigOperatingRate";
         case OMX_IndexParamConsumerUsageBits:           return "ParamConsumerUsageBits";
+        case OMX_IndexParamConsumerUsageBits64:         return "ParamConsumerUsageBits64";
         case OMX_IndexConfigLatency:                    return "ConfigLatency";
         default:                                        return asString((OMX_INDEXTYPE)i, def);
     }
diff --git a/headers/media_plugin/media/openmax/OMX_IndexExt.h b/headers/media_plugin/media/openmax/OMX_IndexExt.h
index 0af40dd..5ddd719 100644
--- a/headers/media_plugin/media/openmax/OMX_IndexExt.h
+++ b/headers/media_plugin/media/openmax/OMX_IndexExt.h
@@ -105,6 +105,7 @@
     OMX_IndexConfigLowLatency,                      /**< reference: OMX_CONFIG_BOOLEANTYPE */
     OMX_IndexConfigAndroidTunnelPeek,               /**< reference: OMX_CONFIG_BOOLEANTYPE */
     OMX_IndexConfigAndroidTunnelPeekLegacyMode,     /**< reference: OMX_CONFIG_BOOLEANTYPE */
+    OMX_IndexParamConsumerUsageBits64,              /**< reference: OMX_PARAM_U64TYPE */
     OMX_IndexExtOtherEndUnused,
 
     /* Time configurations */
diff --git a/include/android/OWNERS b/include/android/OWNERS
new file mode 100644
index 0000000..38f9c55
--- /dev/null
+++ b/include/android/OWNERS
@@ -0,0 +1 @@
+per-file input.h, keycodes.h = file:platform/frameworks/base:/INPUT_OWNERS
diff --git a/include/ftl/details/optional.h b/include/ftl/details/optional.h
index bff7c1e..e45c1f5 100644
--- a/include/ftl/details/optional.h
+++ b/include/ftl/details/optional.h
@@ -54,5 +54,15 @@
 template <typename F, typename T>
 using and_then_result_t = typename and_then_result<F, T>::type;
 
+template <typename F, typename T>
+struct or_else_result {
+  using type = remove_cvref_t<std::invoke_result_t<F>>;
+  static_assert(std::is_same_v<type, std::optional<T>> || std::is_same_v<type, Optional<T>>,
+                "or_else function must return an optional T");
+};
+
+template <typename F, typename T>
+using or_else_result_t = typename or_else_result<F, T>::type;
+
 }  // namespace details
 }  // namespace android::ftl
diff --git a/include/ftl/optional.h b/include/ftl/optional.h
index a818128..94d8e3d 100644
--- a/include/ftl/optional.h
+++ b/include/ftl/optional.h
@@ -96,13 +96,25 @@
     return R();
   }
 
+  // Returns this Optional<T> if not nullopt, or else the Optional<T> returned by the function F.
+  template <typename F>
+  constexpr auto or_else(F&& f) const& -> details::or_else_result_t<F, T> {
+    if (has_value()) return *this;
+    return std::forward<F>(f)();
+  }
+
+  template <typename F>
+  constexpr auto or_else(F&& f) && -> details::or_else_result_t<F, T> {
+    if (has_value()) return std::move(*this);
+    return std::forward<F>(f)();
+  }
+
   // Delete new for this class. Its base doesn't have a virtual destructor, and
   // if it got deleted via base class pointer, it would cause undefined
   // behavior. There's not a good reason to allocate this object on the heap
   // anyway.
   static void* operator new(size_t) = delete;
   static void* operator new[](size_t) = delete;
-
 };
 
 template <typename T, typename U>
diff --git a/include/input/Input.h b/include/input/Input.h
index 608519b..e8af5f7 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -855,6 +855,8 @@
                                                const PointerCoords&);
     static PointerCoords calculateTransformedCoords(uint32_t source, const ui::Transform&,
                                                     const PointerCoords&);
+    // The rounding precision for transformed motion events.
+    static constexpr float ROUNDING_PRECISION = 0.001f;
 
 protected:
     int32_t mAction;
diff --git a/include/input/KeyCharacterMap.h b/include/input/KeyCharacterMap.h
index b5e6f65..c67310e 100644
--- a/include/input/KeyCharacterMap.h
+++ b/include/input/KeyCharacterMap.h
@@ -235,7 +235,7 @@
     KeyedVector<int32_t, Key*> mKeys;
     KeyboardType mType;
     std::string mLoadFileName;
-    bool mLayoutOverlayApplied;
+    bool mLayoutOverlayApplied = false;
 
     std::map<int32_t /* fromAndroidKeyCode */, int32_t /* toAndroidKeyCode */> mKeyRemapping;
     std::map<int32_t /* fromScanCode */, int32_t /* toAndroidKeyCode */> mKeysByScanCode;
diff --git a/include/input/MotionPredictor.h b/include/input/MotionPredictor.h
index 68ebf75..de8ddca 100644
--- a/include/input/MotionPredictor.h
+++ b/include/input/MotionPredictor.h
@@ -22,6 +22,7 @@
 #include <string>
 #include <unordered_map>
 
+#include <android-base/result.h>
 #include <android-base/thread_annotations.h>
 #include <android/sysprop/InputProperties.sysprop.h>
 #include <input/Input.h>
@@ -66,21 +67,27 @@
      * checkEnableMotionPredition: the function to check whether the prediction should run. Used to
      * provide an additional way of turning prediction on and off. Can be toggled at runtime.
      */
-    MotionPredictor(nsecs_t predictionTimestampOffsetNanos, const char* modelPath = nullptr,
+    MotionPredictor(nsecs_t predictionTimestampOffsetNanos,
                     std::function<bool()> checkEnableMotionPrediction = isMotionPredictionEnabled);
-    void record(const MotionEvent& event);
-    std::vector<std::unique_ptr<MotionEvent>> predict(nsecs_t timestamp);
+    /**
+     * Record the actual motion received by the view. This event will be used for calculating the
+     * predictions.
+     *
+     * @return empty result if the event was processed correctly, error if the event is not
+     * consistent with the previously recorded events.
+     */
+    android::base::Result<void> record(const MotionEvent& event);
+    std::unique_ptr<MotionEvent> predict(nsecs_t timestamp);
     bool isPredictionAvailable(int32_t deviceId, int32_t source);
 
 private:
     const nsecs_t mPredictionTimestampOffsetNanos;
-    const std::string mModelPath;
     const std::function<bool()> mCheckMotionPredictionEnabled;
 
     std::unique_ptr<TfLiteMotionPredictorModel> mModel;
-    // Buffers/events for each device seen by record().
-    std::unordered_map</*deviceId*/ int32_t, TfLiteMotionPredictorBuffers> mDeviceBuffers;
-    std::unordered_map</*deviceId*/ int32_t, MotionEvent> mLastEvents;
+
+    std::unique_ptr<TfLiteMotionPredictorBuffers> mBuffers;
+    std::optional<MotionEvent> mLastEvent;
 };
 
 } // namespace android
diff --git a/include/input/PropertyMap.h b/include/input/PropertyMap.h
index 28e4816..2e44142 100644
--- a/include/input/PropertyMap.h
+++ b/include/input/PropertyMap.h
@@ -18,7 +18,11 @@
 
 #include <android-base/result.h>
 #include <utils/Tokenizer.h>
+
+#include <optional>
+#include <string>
 #include <unordered_map>
+#include <unordered_set>
 
 namespace android {
 
@@ -57,14 +61,18 @@
      */
     void addProperty(const std::string& key, const std::string& value);
 
-    /* Gets the value of a property and parses it.
-     * Returns true and sets outValue if the key was found and its value was parsed successfully.
-     * Otherwise returns false and does not modify outValue.  (Also logs a warning.)
+    /* Returns a set of all property keys starting with the given prefix. */
+    std::unordered_set<std::string> getKeysWithPrefix(const std::string& prefix) const;
+
+    /* Gets the value of a property and parses it. Returns nullopt if the key wasn't found or
+     * couldn't be parsed as the requested type. (Warnings are also logged in the case of parsing
+     * failures.)
      */
-    bool tryGetProperty(const std::string& key, std::string& outValue) const;
-    bool tryGetProperty(const std::string& key, bool& outValue) const;
-    bool tryGetProperty(const std::string& key, int32_t& outValue) const;
-    bool tryGetProperty(const std::string& key, float& outValue) const;
+    std::optional<std::string> getString(const std::string& key) const;
+    std::optional<bool> getBool(const std::string& key) const;
+    std::optional<int32_t> getInt(const std::string& key) const;
+    std::optional<float> getFloat(const std::string& key) const;
+    std::optional<double> getDouble(const std::string& key) const;
 
     /* Adds all values from the specified property map. */
     void addAll(const PropertyMap* map);
diff --git a/include/input/TfLiteMotionPredictor.h b/include/input/TfLiteMotionPredictor.h
index 54e2851..7de551b41 100644
--- a/include/input/TfLiteMotionPredictor.h
+++ b/include/input/TfLiteMotionPredictor.h
@@ -99,7 +99,7 @@
 class TfLiteMotionPredictorModel {
 public:
     // Creates a model from an encoded Flatbuffer model.
-    static std::unique_ptr<TfLiteMotionPredictorModel> create(const char* modelPath);
+    static std::unique_ptr<TfLiteMotionPredictorModel> create();
 
     ~TfLiteMotionPredictorModel();
 
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index 6a354b4..3f19371 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -191,6 +191,7 @@
         "google-*",
         "misc-*",
         "performance*",
+        "-performance-move-const-arg", // b/273486801
         "portability*",
     ],
 }
diff --git a/libs/binder/RecordedTransaction.cpp b/libs/binder/RecordedTransaction.cpp
index 2e70304..ef58ed3 100644
--- a/libs/binder/RecordedTransaction.cpp
+++ b/libs/binder/RecordedTransaction.cpp
@@ -16,6 +16,7 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/scopeguard.h>
 #include <android-base/unique_fd.h>
 #include <binder/RecordedTransaction.h>
 #include <sys/mman.h>
@@ -126,8 +127,7 @@
     t.mData.mInterfaceName = std::string(String8(interfaceName).string());
     if (interfaceName.size() != t.mData.mInterfaceName.size()) {
         LOG(ERROR) << "Interface Name is not valid. Contains characters that aren't single byte "
-                      "utf-8: "
-                   << interfaceName;
+                      "utf-8.";
         return std::nullopt;
     }
 
@@ -176,13 +176,33 @@
     RecordedTransaction t;
     ChunkDescriptor chunk;
     const long pageSize = sysconf(_SC_PAGE_SIZE);
+    struct stat fileStat;
+    if (fstat(fd.get(), &fileStat) != 0) {
+        LOG(ERROR) << "Unable to get file information";
+        return std::nullopt;
+    }
+
+    off_t fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR);
+    if (fdCurrentPosition == -1) {
+        LOG(ERROR) << "Invalid offset in file descriptor.";
+        return std::nullopt;
+    }
     do {
+        if (fileStat.st_size < (fdCurrentPosition + (off_t)sizeof(ChunkDescriptor))) {
+            LOG(ERROR) << "Not enough file remains to contain expected chunk descriptor";
+            return std::nullopt;
+        }
         transaction_checksum_t checksum = 0;
         if (NO_ERROR != readChunkDescriptor(fd, &chunk, &checksum)) {
             LOG(ERROR) << "Failed to read chunk descriptor.";
             return std::nullopt;
         }
-        off_t fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR);
+
+        fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR);
+        if (fdCurrentPosition == -1) {
+            LOG(ERROR) << "Invalid offset in file descriptor.";
+            return std::nullopt;
+        }
         off_t mmapPageAlignedStart = (fdCurrentPosition / pageSize) * pageSize;
         off_t mmapPayloadStartOffset = fdCurrentPosition - mmapPageAlignedStart;
 
@@ -194,14 +214,24 @@
         size_t chunkPayloadSize =
                 chunk.dataSize + PADDING8(chunk.dataSize) + sizeof(transaction_checksum_t);
 
+        if (chunkPayloadSize > (size_t)(fileStat.st_size - fdCurrentPosition)) {
+            LOG(ERROR) << "Chunk payload exceeds remaining file size.";
+            return std::nullopt;
+        }
+
         if (PADDING8(chunkPayloadSize) != 0) {
             LOG(ERROR) << "Invalid chunk size, not aligned " << chunkPayloadSize;
             return std::nullopt;
         }
 
-        transaction_checksum_t* payloadMap = reinterpret_cast<transaction_checksum_t*>(
-                mmap(NULL, chunkPayloadSize + mmapPayloadStartOffset, PROT_READ, MAP_SHARED,
-                     fd.get(), mmapPageAlignedStart));
+        size_t memoryMappedSize = chunkPayloadSize + mmapPayloadStartOffset;
+        void* mappedMemory =
+                mmap(NULL, memoryMappedSize, PROT_READ, MAP_SHARED, fd.get(), mmapPageAlignedStart);
+        auto mmap_guard = android::base::make_scope_guard(
+                [mappedMemory, memoryMappedSize] { munmap(mappedMemory, memoryMappedSize); });
+
+        transaction_checksum_t* payloadMap =
+                reinterpret_cast<transaction_checksum_t*>(mappedMemory);
         payloadMap += mmapPayloadStartOffset /
                 sizeof(transaction_checksum_t); // Skip chunk descriptor and required mmap
                                                 // page-alignment
@@ -218,7 +248,12 @@
             LOG(ERROR) << "Checksum failed.";
             return std::nullopt;
         }
-        lseek(fd.get(), chunkPayloadSize, SEEK_CUR);
+
+        fdCurrentPosition = lseek(fd.get(), chunkPayloadSize, SEEK_CUR);
+        if (fdCurrentPosition == -1) {
+            LOG(ERROR) << "Invalid offset in file descriptor.";
+            return std::nullopt;
+        }
 
         switch (chunk.chunkType) {
             case HEADER_CHUNK: {
@@ -255,7 +290,7 @@
                 break;
             default:
                 LOG(INFO) << "Unrecognized chunk.";
-                continue;
+                break;
         }
     } while (chunk.chunkType != END_CHUNK);
 
diff --git a/libs/binder/RpcSession.cpp b/libs/binder/RpcSession.cpp
index ce6ef2b..fbad0f7 100644
--- a/libs/binder/RpcSession.cpp
+++ b/libs/binder/RpcSession.cpp
@@ -20,6 +20,7 @@
 
 #include <dlfcn.h>
 #include <inttypes.h>
+#include <netinet/tcp.h>
 #include <poll.h>
 #include <unistd.h>
 
@@ -90,16 +91,16 @@
     return mMaxIncomingThreads;
 }
 
-void RpcSession::setMaxOutgoingThreads(size_t threads) {
+void RpcSession::setMaxOutgoingConnections(size_t connections) {
     RpcMutexLockGuard _l(mMutex);
     LOG_ALWAYS_FATAL_IF(mStartedSetup,
                         "Must set max outgoing threads before setting up connections");
-    mMaxOutgoingThreads = threads;
+    mMaxOutgoingConnections = connections;
 }
 
 size_t RpcSession::getMaxOutgoingThreads() {
     RpcMutexLockGuard _l(mMutex);
-    return mMaxOutgoingThreads;
+    return mMaxOutgoingConnections;
 }
 
 bool RpcSession::setProtocolVersionInternal(uint32_t version, bool checkStarted) {
@@ -558,11 +559,11 @@
         return status;
     }
 
-    size_t outgoingThreads = std::min(numThreadsAvailable, mMaxOutgoingThreads);
-    ALOGI_IF(outgoingThreads != numThreadsAvailable,
+    size_t outgoingConnections = std::min(numThreadsAvailable, mMaxOutgoingConnections);
+    ALOGI_IF(outgoingConnections != numThreadsAvailable,
              "Server hints client to start %zu outgoing threads, but client will only start %zu "
              "because it is preconfigured to start at most %zu outgoing threads.",
-             numThreadsAvailable, outgoingThreads, mMaxOutgoingThreads);
+             numThreadsAvailable, outgoingConnections, mMaxOutgoingConnections);
 
     // TODO(b/189955605): we should add additional sessions dynamically
     // instead of all at once - the other side should be responsible for setting
@@ -571,10 +572,10 @@
     // any requests at all.
 
     // we've already setup one client
-    LOG_RPC_DETAIL("RpcSession::setupClient() instantiating %zu outgoing (server max: %zu) and %zu "
-                   "incoming threads",
-                   outgoingThreads, numThreadsAvailable, mMaxIncomingThreads);
-    for (size_t i = 0; i + 1 < outgoingThreads; i++) {
+    LOG_RPC_DETAIL("RpcSession::setupClient() instantiating %zu outgoing connections (server max: "
+                   "%zu) and %zu incoming threads",
+                   outgoingConnections, numThreadsAvailable, mMaxIncomingThreads);
+    for (size_t i = 0; i + 1 < outgoingConnections; i++) {
         if (status_t status = connectAndInit(mId, false /*incoming*/); status != OK) return status;
     }
 
@@ -608,6 +609,18 @@
             return -savedErrno;
         }
 
+        if (addr.addr()->sa_family == AF_INET || addr.addr()->sa_family == AF_INET6) {
+            int noDelay = 1;
+            int result =
+                    setsockopt(serverFd.get(), IPPROTO_TCP, TCP_NODELAY, &noDelay, sizeof(noDelay));
+            if (result < 0) {
+                int savedErrno = errno;
+                ALOGE("Could not set TCP_NODELAY on %s: %s", addr.toString().c_str(),
+                      strerror(savedErrno));
+                return -savedErrno;
+            }
+        }
+
         RpcTransportFd transportFd(std::move(serverFd));
 
         if (0 != TEMP_FAILURE_RETRY(connect(transportFd.fd.get(), addr.addr(), addr.addrSize()))) {
@@ -932,7 +945,8 @@
                   (session->server()
                            ? "This is a server session, so see RpcSession::setMaxIncomingThreads "
                              "for the corresponding client"
-                           : "This is a client session, so see RpcSession::setMaxOutgoingThreads "
+                           : "This is a client session, so see "
+                             "RpcSession::setMaxOutgoingConnections "
                              "for this client or RpcServer::setMaxThreads for the corresponding "
                              "server"));
             return WOULD_BLOCK;
diff --git a/libs/binder/RpcState.cpp b/libs/binder/RpcState.cpp
index b27f102..ed3ce24 100644
--- a/libs/binder/RpcState.cpp
+++ b/libs/binder/RpcState.cpp
@@ -262,8 +262,10 @@
 }
 
 void RpcState::clear() {
-    RpcMutexUniqueLock _l(mNodeMutex);
+    return clear(RpcMutexUniqueLock(mNodeMutex));
+}
 
+void RpcState::clear(RpcMutexUniqueLock nodeLock) {
     if (mTerminated) {
         LOG_ALWAYS_FATAL_IF(!mNodeForAddress.empty(),
                             "New state should be impossible after terminating!");
@@ -292,7 +294,7 @@
     auto temp = std::move(mNodeForAddress);
     mNodeForAddress.clear(); // RpcState isn't reusable, but for future/explicit
 
-    _l.unlock();
+    nodeLock.unlock();
     temp.clear(); // explicit
 }
 
@@ -557,13 +559,12 @@
             .parcelDataSize = static_cast<uint32_t>(data.dataSize()),
     };
 
-    constexpr size_t kWaitMaxUs = 1000000;
-    constexpr size_t kWaitLogUs = 10000;
-    size_t waitUs = 0;
-
     // Oneway calls have no sync point, so if many are sent before, whether this
     // is a twoway or oneway transaction, they may have filled up the socket.
     // So, make sure we drain them before polling
+    constexpr size_t kWaitMaxUs = 1000000;
+    constexpr size_t kWaitLogUs = 10000;
+    size_t waitUs = 0;
 
     iovec iovs[]{
             {&command, sizeof(RpcWireHeader)},
@@ -591,8 +592,9 @@
                 },
                 rpcFields->mFds.get());
         status != OK) {
-        // TODO(b/167966510): need to undo onBinderLeaving - we know the
-        // refcount isn't successfully transferred.
+        // rpcSend calls shutdownAndWait, so all refcounts should be reset. If we ever tolerate
+        // errors here, then we may need to undo the binder-sent counts for the transaction as
+        // well as for the binder objects in the Parcel
         return status;
     }
 
@@ -704,7 +706,7 @@
     };
 
     {
-        RpcMutexLockGuard _l(mNodeMutex);
+        RpcMutexUniqueLock _l(mNodeMutex);
         if (mTerminated) return DEAD_OBJECT; // avoid fatal only, otherwise races
         auto it = mNodeForAddress.find(addr);
         LOG_ALWAYS_FATAL_IF(it == mNodeForAddress.end(),
@@ -720,8 +722,9 @@
         body.amount = it->second.timesRecd - target;
         it->second.timesRecd = target;
 
-        LOG_ALWAYS_FATAL_IF(nullptr != tryEraseNode(it),
+        LOG_ALWAYS_FATAL_IF(nullptr != tryEraseNode(session, std::move(_l), it),
                             "Bad state. RpcState shouldn't own received binder");
+        // LOCK ALREADY RELEASED
     }
 
     RpcWireHeader cmd = {
@@ -1036,8 +1039,8 @@
                 return DEAD_OBJECT;
             }
 
-            if (it->second.asyncTodo.size() == 0) return OK;
-            if (it->second.asyncTodo.top().asyncNumber == it->second.asyncNumber) {
+            if (it->second.asyncTodo.size() != 0 &&
+                it->second.asyncTodo.top().asyncNumber == it->second.asyncNumber) {
                 LOG_RPC_DETAIL("Found next async transaction %" PRIu64 " on %" PRIu64,
                                it->second.asyncNumber, addr);
 
@@ -1164,8 +1167,8 @@
                    it->second.timesSent);
 
     it->second.timesSent -= body.amount;
-    sp<IBinder> tempHold = tryEraseNode(it);
-    _l.unlock();
+    sp<IBinder> tempHold = tryEraseNode(session, std::move(_l), it);
+    // LOCK ALREADY RELEASED
     tempHold = nullptr; // destructor may make binder calls on this session
 
     return OK;
@@ -1229,7 +1232,10 @@
     return OK;
 }
 
-sp<IBinder> RpcState::tryEraseNode(std::map<uint64_t, BinderNode>::iterator& it) {
+sp<IBinder> RpcState::tryEraseNode(const sp<RpcSession>& session, RpcMutexUniqueLock nodeLock,
+                                   std::map<uint64_t, BinderNode>::iterator& it) {
+    bool shouldShutdown = false;
+
     sp<IBinder> ref;
 
     if (it->second.timesSent == 0) {
@@ -1239,9 +1245,27 @@
             LOG_ALWAYS_FATAL_IF(!it->second.asyncTodo.empty(),
                                 "Can't delete binder w/ pending async transactions");
             mNodeForAddress.erase(it);
+
+            if (mNodeForAddress.size() == 0) {
+                shouldShutdown = true;
+            }
         }
     }
 
+    // If we shutdown, prevent RpcState from being re-used. This prevents another
+    // thread from getting the root object again.
+    if (shouldShutdown) {
+        clear(std::move(nodeLock));
+    } else {
+        nodeLock.unlock(); // explicit
+    }
+    // LOCK IS RELEASED
+
+    if (shouldShutdown) {
+        ALOGI("RpcState has no binders left, so triggering shutdown...");
+        (void)session->shutdownAndWait(false);
+    }
+
     return ref;
 }
 
diff --git a/libs/binder/RpcState.h b/libs/binder/RpcState.h
index ac86585..0e23ea7 100644
--- a/libs/binder/RpcState.h
+++ b/libs/binder/RpcState.h
@@ -168,6 +168,7 @@
     void clear();
 
 private:
+    void clear(RpcMutexUniqueLock nodeLock);
     void dumpLocked();
 
     // Alternative to std::vector<uint8_t> that doesn't abort on allocation failure and caps
@@ -268,11 +269,20 @@
         std::string toString() const;
     };
 
-    // checks if there is any reference left to a node and erases it. If erase
-    // happens, and there is a strong reference to the binder kept by
-    // binderNode, this returns that strong reference, so that it can be
-    // dropped after any locks are removed.
-    sp<IBinder> tryEraseNode(std::map<uint64_t, BinderNode>::iterator& it);
+    // Checks if there is any reference left to a node and erases it. If this
+    // is the last node, shuts down the session.
+    //
+    // Node lock is passed here for convenience, so that we can release it
+    // and terminate the session, but we could leave it up to the caller
+    // by returning a continuation if we needed to erase multiple specific
+    // nodes. It may be tempting to allow the client to keep on holding the
+    // lock and instead just return whether or not we should shutdown, but
+    // this introduces the posssibility that another thread calls
+    // getRootBinder and thinks it is valid, rather than immediately getting
+    // an error.
+    sp<IBinder> tryEraseNode(const sp<RpcSession>& session, RpcMutexUniqueLock nodeLock,
+                             std::map<uint64_t, BinderNode>::iterator& it);
+
     // true - success
     // false - session shutdown, halt
     [[nodiscard]] bool nodeProgressAsyncNumber(BinderNode* node);
diff --git a/libs/binder/ServiceManagerHost.cpp b/libs/binder/ServiceManagerHost.cpp
index 194254a..2b67f03 100644
--- a/libs/binder/ServiceManagerHost.cpp
+++ b/libs/binder/ServiceManagerHost.cpp
@@ -159,8 +159,8 @@
     LOG_ALWAYS_FATAL_IF(!forwardResult->hostPort().has_value());
 
     auto rpcSession = RpcSession::make();
-    if (options.maxOutgoingThreads.has_value()) {
-        rpcSession->setMaxOutgoingThreads(*options.maxOutgoingThreads);
+    if (options.maxOutgoingConnections.has_value()) {
+        rpcSession->setMaxOutgoingConnections(*options.maxOutgoingConnections);
     }
 
     if (status_t status = rpcSession->setupInetClient("127.0.0.1", *forwardResult->hostPort());
diff --git a/libs/binder/TEST_MAPPING b/libs/binder/TEST_MAPPING
index 1488400..07b38d7 100644
--- a/libs/binder/TEST_MAPPING
+++ b/libs/binder/TEST_MAPPING
@@ -119,5 +119,10 @@
     {
       "name": "memunreachable_binder_test"
     }
+  ],
+  "imports": [
+    {
+      "path": "packages/modules/Virtualization"
+    }
   ]
 }
diff --git a/libs/binder/include/binder/IServiceManager.h b/libs/binder/include/binder/IServiceManager.h
index c78f870..55167a7 100644
--- a/libs/binder/include/binder/IServiceManager.h
+++ b/libs/binder/include/binder/IServiceManager.h
@@ -224,12 +224,12 @@
 //    }
 // Resources are cleaned up when the object is destroyed.
 //
-// For each returned binder object, at most |maxOutgoingThreads| outgoing threads are instantiated.
-// Hence, only |maxOutgoingThreads| calls can be made simultaneously. Additional calls are blocked
-// if there are |maxOutgoingThreads| ongoing calls. See RpcSession::setMaxOutgoingThreads.
-// If |maxOutgoingThreads| is not set, default is |RpcSession::kDefaultMaxOutgoingThreads|.
+// For each returned binder object, at most |maxOutgoingConnections| outgoing connections are
+// instantiated, depending on how many the service on the device is configured with.
+// Hence, only |maxOutgoingConnections| calls can be made simultaneously.
+// See also RpcSession::setMaxOutgoingConnections.
 struct RpcDelegateServiceManagerOptions {
-    std::optional<size_t> maxOutgoingThreads;
+    std::optional<size_t> maxOutgoingConnections;
 };
 sp<IServiceManager> createRpcDelegateServiceManager(
         const RpcDelegateServiceManagerOptions& options);
diff --git a/libs/binder/include/binder/RpcServer.h b/libs/binder/include/binder/RpcServer.h
index 25193a3..1001b64 100644
--- a/libs/binder/include/binder/RpcServer.h
+++ b/libs/binder/include/binder/RpcServer.h
@@ -119,7 +119,10 @@
     [[nodiscard]] status_t setupExternalServer(base::unique_fd serverFd);
 
     /**
-     * This must be called before adding a client session.
+     * This must be called before adding a client session. This corresponds
+     * to the number of incoming connections to RpcSession objects in the
+     * server, which will correspond to the number of outgoing connections
+     * in client RpcSession objects.
      *
      * If this is not specified, this will be a single-threaded server.
      *
diff --git a/libs/binder/include/binder/RpcSession.h b/libs/binder/include/binder/RpcSession.h
index 40faf2c..a323feb 100644
--- a/libs/binder/include/binder/RpcSession.h
+++ b/libs/binder/include/binder/RpcSession.h
@@ -51,11 +51,12 @@
  * This represents a session (group of connections) between a client
  * and a server. Multiple connections are needed for multiple parallel "binder"
  * calls which may also have nested calls.
+ *
+ * Once a binder exists in the session, if all references to all binders are dropped,
+ * the session shuts down.
  */
 class RpcSession final : public virtual RefBase {
 public:
-    static constexpr size_t kDefaultMaxOutgoingThreads = 10;
-
     // Create an RpcSession with default configuration (raw sockets).
     static sp<RpcSession> make();
 
@@ -67,26 +68,30 @@
     /**
      * Set the maximum number of incoming threads allowed to be made (for things like callbacks).
      * By default, this is 0. This must be called before setting up this connection as a client.
-     * Server sessions will inherits this value from RpcServer.
+     * Server sessions will inherits this value from RpcServer. Each thread will serve a
+     * connection to the remote RpcSession.
      *
      * If this is called, 'shutdown' on this session must also be called.
      * Otherwise, a threadpool will leak.
      *
-     * TODO(b/189955605): start these dynamically
+     * TODO(b/189955605): start these lazily - currently all are started
      */
     void setMaxIncomingThreads(size_t threads);
     size_t getMaxIncomingThreads();
 
     /**
-     * Set the maximum number of outgoing threads allowed to be made.
-     * By default, this is |kDefaultMaxOutgoingThreads|. This must be called before setting up this
-     * connection as a client.
+     * Set the maximum number of outgoing connections allowed to be made.
+     * By default, this is |kDefaultMaxOutgoingConnections|. This must be called before setting up
+     * this connection as a client.
      *
-     * This limits the number of outgoing threads on top of the remote peer setting. This RpcSession
-     * will only instantiate |min(maxOutgoingThreads, remoteMaxThreads)| outgoing threads, where
-     * |remoteMaxThreads| can be retrieved from the remote peer via |getRemoteMaxThreads()|.
+     * For an RpcSession client, if you are connecting to a server which starts N threads,
+     * then this must be set to >= N. If you set the maximum number of outgoing connections
+     * to 1, but the server requests 10, then it would be considered an error. If you set a
+     * maximum number of connections to 10, and the server requests 1, then only 1 will be
+     * created. This API is used to limit the amount of resources a server can request you
+     * create.
      */
-    void setMaxOutgoingThreads(size_t threads);
+    void setMaxOutgoingConnections(size_t connections);
     size_t getMaxOutgoingThreads();
 
     /**
@@ -219,6 +224,8 @@
     friend RpcState;
     explicit RpcSession(std::unique_ptr<RpcTransportCtx> ctx);
 
+    static constexpr size_t kDefaultMaxOutgoingConnections = 10;
+
     // internal version of setProtocolVersion that
     // optionally skips the mStartedSetup check
     [[nodiscard]] bool setProtocolVersionInternal(uint32_t version, bool checkStarted);
@@ -368,7 +375,7 @@
 
     bool mStartedSetup = false;
     size_t mMaxIncomingThreads = 0;
-    size_t mMaxOutgoingThreads = kDefaultMaxOutgoingThreads;
+    size_t mMaxOutgoingConnections = kDefaultMaxOutgoingConnections;
     std::optional<uint32_t> mProtocolVersion;
     FileDescriptorTransportMode mFileDescriptorTransportMode = FileDescriptorTransportMode::NONE;
 
diff --git a/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp b/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
index 42d226b..a157792 100644
--- a/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
+++ b/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
@@ -126,11 +126,11 @@
 void ARpcSession_setFileDescriptorTransportMode(ARpcSession* session,
                                                 ARpcSession_FileDescriptorTransportMode mode);
 
-// Sets the maximum number of incoming threads.
+// Sets the maximum number of incoming threads, to service connections.
 void ARpcSession_setMaxIncomingThreads(ARpcSession* session, size_t threads);
 
-// Sets the maximum number of outgoing threads.
-void ARpcSession_setMaxOutgoingThreads(ARpcSession* session, size_t threads);
+// Sets the maximum number of outgoing connections.
+void ARpcSession_setMaxOutgoingConnections(ARpcSession* session, size_t connections);
 
 // Decrements the refcount of the underlying RpcSession object.
 void ARpcSession_free(ARpcSession* session);
diff --git a/libs/binder/libbinder_rpc_unstable.cpp b/libs/binder/libbinder_rpc_unstable.cpp
index daff8c1..a167f23 100644
--- a/libs/binder/libbinder_rpc_unstable.cpp
+++ b/libs/binder/libbinder_rpc_unstable.cpp
@@ -265,8 +265,8 @@
     session->setMaxIncomingThreads(threads);
 }
 
-void ARpcSession_setMaxOutgoingThreads(ARpcSession* handle, size_t threads) {
+void ARpcSession_setMaxOutgoingConnections(ARpcSession* handle, size_t connections) {
     auto session = handleToStrongPointer<RpcSession>(handle);
-    session->setMaxOutgoingThreads(threads);
+    session->setMaxOutgoingConnections(connections);
 }
 }
diff --git a/libs/binder/ndk/include_ndk/android/binder_parcel.h b/libs/binder/ndk/include_ndk/android/binder_parcel.h
index f68612c..d833b83 100644
--- a/libs/binder/ndk/include_ndk/android/binder_parcel.h
+++ b/libs/binder/ndk/include_ndk/android/binder_parcel.h
@@ -26,11 +26,11 @@
 
 #pragma once
 
+#include <android/binder_status.h>
 #include <stdbool.h>
 #include <stddef.h>
 #include <sys/cdefs.h>
-
-#include <android/binder_status.h>
+#include <uchar.h>
 
 struct AIBinder;
 typedef struct AIBinder AIBinder;
diff --git a/libs/binder/ndk/include_platform/android/binder_process.h b/libs/binder/ndk/include_platform/android/binder_process.h
index ffcad55..3fbe90d 100644
--- a/libs/binder/ndk/include_platform/android/binder_process.h
+++ b/libs/binder/ndk/include_platform/android/binder_process.h
@@ -32,7 +32,7 @@
  * Do not use this from a library. Apps setup their own threadpools, and otherwise, the main
  * function should be responsible for configuring the threadpool for the entire application.
  */
-void ABinderProcess_startThreadPool();
+void ABinderProcess_startThreadPool(void);
 /**
  * This sets the maximum number of threads that can be started in the threadpool. By default, after
  * startThreadPool is called, this is 15. If it is called additional times, it will only prevent
@@ -48,7 +48,7 @@
  * you should use this in a library to abort if the threadpool is not started.
  * Programs should configure binder threadpools once at the beginning.
  */
-bool ABinderProcess_isThreadPoolStarted();
+bool ABinderProcess_isThreadPoolStarted(void);
 /**
  * This adds the current thread to the threadpool. This may cause the threadpool to exceed the
  * maximum size.
@@ -56,7 +56,7 @@
  * Do not use this from a library. Apps setup their own threadpools, and otherwise, the main
  * function should be responsible for configuring the threadpool for the entire application.
  */
-void ABinderProcess_joinThreadPool();
+void ABinderProcess_joinThreadPool(void);
 
 /**
  * This gives you an fd to wait on. Whenever data is available on the fd,
@@ -79,6 +79,6 @@
  *
  * \return STATUS_OK on success
  */
-__attribute__((weak)) binder_status_t ABinderProcess_handlePolledCommands() __INTRODUCED_IN(31);
+__attribute__((weak)) binder_status_t ABinderProcess_handlePolledCommands(void) __INTRODUCED_IN(31);
 
 __END_DECLS
diff --git a/libs/binder/ndk/process.cpp b/libs/binder/ndk/process.cpp
index bc6610e..0fea57b 100644
--- a/libs/binder/ndk/process.cpp
+++ b/libs/binder/ndk/process.cpp
@@ -24,17 +24,17 @@
 using ::android::IPCThreadState;
 using ::android::ProcessState;
 
-void ABinderProcess_startThreadPool() {
+void ABinderProcess_startThreadPool(void) {
     ProcessState::self()->startThreadPool();
     ProcessState::self()->giveThreadPoolName();
 }
 bool ABinderProcess_setThreadPoolMaxThreadCount(uint32_t numThreads) {
     return ProcessState::self()->setThreadPoolMaxThreadCount(numThreads) == 0;
 }
-bool ABinderProcess_isThreadPoolStarted() {
+bool ABinderProcess_isThreadPoolStarted(void) {
     return ProcessState::self()->isThreadPoolStarted();
 }
-void ABinderProcess_joinThreadPool() {
+void ABinderProcess_joinThreadPool(void) {
     IPCThreadState::self()->joinThreadPool();
 }
 
@@ -42,6 +42,6 @@
     return IPCThreadState::self()->setupPolling(fd);
 }
 
-binder_status_t ABinderProcess_handlePolledCommands() {
+binder_status_t ABinderProcess_handlePolledCommands(void) {
     return IPCThreadState::self()->handlePolledCommands();
 }
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index 5b2532a..882f1d6 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -52,7 +52,7 @@
 constexpr char kForcePersistNdkUnitTestService[] = "ForcePersistNdkUnitTestService";
 constexpr char kActiveServicesNdkUnitTestService[] = "ActiveServicesNdkUnitTestService";
 
-constexpr unsigned int kShutdownWaitTime = 10;
+constexpr unsigned int kShutdownWaitTime = 11;
 constexpr uint64_t kContextTestValue = 0xb4e42fb4d9a1d715;
 
 class MyTestFoo : public IFoo {
diff --git a/libs/binder/rust/Android.bp b/libs/binder/rust/Android.bp
index afd414a..d36ebac 100644
--- a/libs/binder/rust/Android.bp
+++ b/libs/binder/rust/Android.bp
@@ -21,6 +21,7 @@
     ],
     host_supported: true,
     vendor_available: true,
+    product_available: true,
     target: {
         darwin: {
             enabled: false,
@@ -72,6 +73,7 @@
     ],
     host_supported: true,
     vendor_available: true,
+    product_available: true,
     target: {
         darwin: {
             enabled: false,
@@ -129,6 +131,7 @@
     ],
     host_supported: true,
     vendor_available: true,
+    product_available: true,
 
     // Currently necessary for host builds
     // TODO(b/31559095): bionic on host should define this
diff --git a/libs/binder/rust/rpcbinder/src/session.rs b/libs/binder/rust/rpcbinder/src/session.rs
index 0b517cf..28c5390 100644
--- a/libs/binder/rust/rpcbinder/src/session.rs
+++ b/libs/binder/rust/rpcbinder/src/session.rs
@@ -75,11 +75,14 @@
         };
     }
 
-    /// Sets the maximum number of outgoing threads.
-    pub fn set_max_outgoing_threads(&self, threads: usize) {
+    /// Sets the maximum number of outgoing connections.
+    pub fn set_max_outgoing_connections(&self, connections: usize) {
         // SAFETY - Only passes the 'self' pointer as an opaque handle.
         unsafe {
-            binder_rpc_unstable_bindgen::ARpcSession_setMaxOutgoingThreads(self.as_ptr(), threads)
+            binder_rpc_unstable_bindgen::ARpcSession_setMaxOutgoingConnections(
+                self.as_ptr(),
+                connections,
+            )
         };
     }
 
diff --git a/libs/binder/rust/src/binder.rs b/libs/binder/rust/src/binder.rs
index 976f54d..d0e35de 100644
--- a/libs/binder/rust/src/binder.rs
+++ b/libs/binder/rust/src/binder.rs
@@ -100,22 +100,17 @@
 /// An interface can promise to be a stable vendor interface ([`Vintf`]), or
 /// makes no stability guarantees ([`Local`]). [`Local`] is
 /// currently the default stability.
-#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
 pub enum Stability {
     /// Default stability, visible to other modules in the same compilation
     /// context (e.g. modules on system.img)
+    #[default]
     Local,
 
     /// A Vendor Interface Object, which promises to be stable
     Vintf,
 }
 
-impl Default for Stability {
-    fn default() -> Self {
-        Stability::Local
-    }
-}
-
 impl From<Stability> for i32 {
     fn from(stability: Stability) -> i32 {
         use Stability::*;
diff --git a/libs/binder/rust/src/native.rs b/libs/binder/rust/src/native.rs
index 6f686fb..5557168 100644
--- a/libs/binder/rust/src/native.rs
+++ b/libs/binder/rust/src/native.rs
@@ -209,8 +209,8 @@
     }
 
     /// Mark this binder object with local stability, which is vendor if we are
-    /// building for the VNDK and system otherwise.
-    #[cfg(any(vendor_ndk, android_vndk))]
+    /// building for android_vendor and system otherwise.
+    #[cfg(android_vendor)]
     fn mark_local_stability(&mut self) {
         unsafe {
             // Safety: Self always contains a valid `AIBinder` pointer, so
@@ -220,8 +220,8 @@
     }
 
     /// Mark this binder object with local stability, which is vendor if we are
-    /// building for the VNDK and system otherwise.
-    #[cfg(not(any(vendor_ndk, android_vndk)))]
+    /// building for android_vendor and system otherwise.
+    #[cfg(not(android_vendor))]
     fn mark_local_stability(&mut self) {
         unsafe {
             // Safety: Self always contains a valid `AIBinder` pointer, so
diff --git a/libs/binder/rust/src/parcel/parcelable.rs b/libs/binder/rust/src/parcel/parcelable.rs
index 6f4c375..4b658fc 100644
--- a/libs/binder/rust/src/parcel/parcelable.rs
+++ b/libs/binder/rust/src/parcel/parcelable.rs
@@ -771,7 +771,13 @@
 #[macro_export]
 macro_rules! impl_serialize_for_parcelable {
     ($parcelable:ident) => {
-        impl $crate::binder_impl::Serialize for $parcelable {
+        $crate::impl_serialize_for_parcelable!($parcelable < >);
+    };
+    ($parcelable:ident < $( $param:ident ),* , >) => {
+        $crate::impl_serialize_for_parcelable!($parcelable < $($param),* >);
+    };
+    ($parcelable:ident < $( $param:ident ),* > ) => {
+        impl < $($param),* > $crate::binder_impl::Serialize for $parcelable < $($param),* > {
             fn serialize(
                 &self,
                 parcel: &mut $crate::binder_impl::BorrowedParcel<'_>,
@@ -780,9 +786,9 @@
             }
         }
 
-        impl $crate::binder_impl::SerializeArray for $parcelable {}
+        impl < $($param),* > $crate::binder_impl::SerializeArray for $parcelable < $($param),* > {}
 
-        impl $crate::binder_impl::SerializeOption for $parcelable {
+        impl < $($param),* > $crate::binder_impl::SerializeOption for $parcelable < $($param),* > {
             fn serialize_option(
                 this: Option<&Self>,
                 parcel: &mut $crate::binder_impl::BorrowedParcel<'_>,
@@ -808,7 +814,13 @@
 #[macro_export]
 macro_rules! impl_deserialize_for_parcelable {
     ($parcelable:ident) => {
-        impl $crate::binder_impl::Deserialize for $parcelable {
+        $crate::impl_deserialize_for_parcelable!($parcelable < >);
+    };
+    ($parcelable:ident < $( $param:ident ),* , >) => {
+        $crate::impl_deserialize_for_parcelable!($parcelable < $($param),* >);
+    };
+    ($parcelable:ident < $( $param:ident ),* > ) => {
+        impl < $($param: Default),* > $crate::binder_impl::Deserialize for $parcelable < $($param),* > {
             fn deserialize(
                 parcel: &$crate::binder_impl::BorrowedParcel<'_>,
             ) -> std::result::Result<Self, $crate::StatusCode> {
@@ -830,9 +842,9 @@
             }
         }
 
-        impl $crate::binder_impl::DeserializeArray for $parcelable {}
+        impl < $($param: Default),* > $crate::binder_impl::DeserializeArray for $parcelable < $($param),* > {}
 
-        impl $crate::binder_impl::DeserializeOption for $parcelable {
+        impl < $($param: Default),* > $crate::binder_impl::DeserializeOption for $parcelable < $($param),* > {
             fn deserialize_option(
                 parcel: &$crate::binder_impl::BorrowedParcel<'_>,
             ) -> std::result::Result<Option<Self>, $crate::StatusCode> {
diff --git a/libs/binder/tests/IBinderRpcTest.aidl b/libs/binder/tests/IBinderRpcTest.aidl
index a3ed571..1164767 100644
--- a/libs/binder/tests/IBinderRpcTest.aidl
+++ b/libs/binder/tests/IBinderRpcTest.aidl
@@ -80,4 +80,8 @@
     // get queued.
     oneway void blockingSendFdOneway(in ParcelFileDescriptor fd);
     ParcelFileDescriptor blockingRecvFd();
+
+    // Same as blockingSendFdOneway, but with integers.
+    oneway void blockingSendIntOneway(int n);
+    int blockingRecvInt();
 }
diff --git a/libs/binder/tests/binderHostDeviceTest.cpp b/libs/binder/tests/binderHostDeviceTest.cpp
index 464da60..77a5fa8 100644
--- a/libs/binder/tests/binderHostDeviceTest.cpp
+++ b/libs/binder/tests/binderHostDeviceTest.cpp
@@ -66,7 +66,7 @@
 void initHostRpcServiceManagerOnce() {
     static std::once_flag gSmOnce;
     std::call_once(gSmOnce, [] {
-        setDefaultServiceManager(createRpcDelegateServiceManager({.maxOutgoingThreads = 1}));
+        setDefaultServiceManager(createRpcDelegateServiceManager({.maxOutgoingConnections = 1}));
     });
 }
 
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index 955c650..8974ad7 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -507,7 +507,13 @@
     }
 
     EXPECT_EQ(-EAGAIN, IPCThreadState::self()->freeze(pid, true, 0));
-    EXPECT_EQ(-EAGAIN, IPCThreadState::self()->freeze(pid, true, 0));
+
+    // b/268232063 - succeeds ~0.08% of the time
+    {
+        auto ret = IPCThreadState::self()->freeze(pid, true, 0);
+        EXPECT_TRUE(ret == -EAGAIN || ret == OK);
+    }
+
     EXPECT_EQ(NO_ERROR, IPCThreadState::self()->freeze(pid, true, 1000));
     EXPECT_EQ(FAILED_TRANSACTION, m_server->transact(BINDER_LIB_TEST_NOP_TRANSACTION, data, &reply));
 
@@ -1370,7 +1376,7 @@
         }));
     }
 
-    data.writeInt32(100);
+    data.writeInt32(500);
     // Give a chance for all threads to be used
     EXPECT_THAT(server->transact(BINDER_LIB_TEST_UNLOCK_AFTER_MS, data, &reply), NO_ERROR);
 
diff --git a/libs/binder/tests/binderRpcBenchmark.cpp b/libs/binder/tests/binderRpcBenchmark.cpp
index 52ba9b0..5939273 100644
--- a/libs/binder/tests/binderRpcBenchmark.cpp
+++ b/libs/binder/tests/binderRpcBenchmark.cpp
@@ -102,9 +102,11 @@
 }
 
 static sp<RpcSession> gSession = RpcSession::make();
+static sp<IBinder> gRpcBinder;
 // Certificate validation happens during handshake and does not affect the result of benchmarks.
 // Skip certificate validation to simplify the setup process.
 static sp<RpcSession> gSessionTls = RpcSession::make(makeFactoryTls());
+static sp<IBinder> gRpcTlsBinder;
 #ifdef __BIONIC__
 static const String16 kKernelBinderInstance = String16(u"binderRpcBenchmark-control");
 static sp<IBinder> gKernelBinder;
@@ -118,9 +120,9 @@
             return gKernelBinder;
 #endif
         case RPC:
-            return gSession->getRootObject();
+            return gRpcBinder;
         case RPC_TLS:
-            return gSessionTls->getRootObject();
+            return gRpcTlsBinder;
         default:
             LOG(FATAL) << "Unknown transport value: " << transport;
             return nullptr;
@@ -254,11 +256,13 @@
     (void)unlink(addr.c_str());
     forkRpcServer(addr.c_str(), RpcServer::make(RpcTransportCtxFactoryRaw::make()));
     setupClient(gSession, addr.c_str());
+    gRpcBinder = gSession->getRootObject();
 
     std::string tlsAddr = tmp + "/binderRpcTlsBenchmark";
     (void)unlink(tlsAddr.c_str());
     forkRpcServer(tlsAddr.c_str(), RpcServer::make(makeFactoryTls()));
     setupClient(gSessionTls, tlsAddr.c_str());
+    gRpcTlsBinder = gSessionTls->getRootObject();
 
     ::benchmark::RunSpecifiedBenchmarks();
     return 0;
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index 84c93dd..8b37ed8 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -129,7 +129,7 @@
 static std::string allocateSocketAddress() {
     static size_t id = 0;
     std::string temp = getenv("TMPDIR") ?: "/tmp";
-    auto ret = temp + "/binderRpcTest_" + std::to_string(id++);
+    auto ret = temp + "/binderRpcTest_" + std::to_string(getpid()) + "_" + std::to_string(id++);
     unlink(ret.c_str());
     return ret;
 };
@@ -163,7 +163,8 @@
             session.root = nullptr;
         }
 
-        for (auto& info : sessions) {
+        for (size_t sessionNum = 0; sessionNum < sessions.size(); sessionNum++) {
+            auto& info = sessions.at(sessionNum);
             sp<RpcSession>& session = info.session;
 
             EXPECT_NE(nullptr, session);
@@ -179,6 +180,7 @@
             for (size_t i = 0; i < 3; i++) {
                 sp<RpcSession> strongSession = weakSession.promote();
                 EXPECT_EQ(nullptr, strongSession)
+                        << "For session " << sessionNum << ". "
                         << (debugBacktrace(host.getPid()), debugBacktrace(getpid()),
                             "Leaked sess: ")
                         << strongSession->getStrongCount() << " checked time " << i;
@@ -231,17 +233,8 @@
     return std::move(sockClient);
 }
 
-std::string BinderRpc::PrintParamInfo(const testing::TestParamInfo<ParamType>& info) {
-    auto [type, security, clientVersion, serverVersion, singleThreaded, noKernel] = info.param;
-    auto ret = PrintToString(type) + "_" + newFactory(security)->toCString() + "_clientV" +
-            std::to_string(clientVersion) + "_serverV" + std::to_string(serverVersion);
-    if (singleThreaded) {
-        ret += "_single_threaded";
-    }
-    if (noKernel) {
-        ret += "_no_kernel";
-    }
-    return ret;
+std::unique_ptr<RpcTransportCtxFactory> BinderRpc::newFactory(RpcSecurity rpcSecurity) {
+    return newTlsFactory(rpcSecurity);
 }
 
 // This creates a new process serving an interface on a certain number of
@@ -250,6 +243,10 @@
         const BinderRpcOptions& options) {
     CHECK_GE(options.numSessions, 1) << "Must have at least one session to a server";
 
+    if (options.numIncomingConnectionsBySession.size() != 0) {
+        CHECK_EQ(options.numIncomingConnectionsBySession.size(), options.numSessions);
+    }
+
     SocketType socketType = std::get<0>(GetParam());
     RpcSecurity rpcSecurity = std::get<1>(GetParam());
     uint32_t clientVersion = std::get<2>(GetParam());
@@ -317,7 +314,7 @@
             LOG_ALWAYS_FATAL("TIPC socket type only supported on vendor");
 #endif
         } else {
-            factory = newFactory(rpcSecurity, certVerifier);
+            factory = newTlsFactory(rpcSecurity, certVerifier);
         }
         sessions.emplace_back(RpcSession::make(std::move(factory)));
     }
@@ -347,10 +344,16 @@
 
     status_t status;
 
-    for (const auto& session : sessions) {
+    for (size_t i = 0; i < sessions.size(); i++) {
+        const auto& session = sessions.at(i);
+
+        size_t numIncoming = options.numIncomingConnectionsBySession.size() > 0
+                ? options.numIncomingConnectionsBySession.at(i)
+                : 0;
+
         CHECK(session->setProtocolVersion(clientVersion));
-        session->setMaxIncomingThreads(options.numIncomingConnections);
-        session->setMaxOutgoingThreads(options.numOutgoingConnections);
+        session->setMaxIncomingThreads(numIncoming);
+        session->setMaxOutgoingConnections(options.numOutgoingConnections);
         session->setFileDescriptorTransportMode(options.clientFileDescriptorTransportMode);
 
         switch (socketType) {
@@ -435,8 +438,7 @@
     for (auto& t : ts) t.join();
 }
 
-static void testThreadPoolOverSaturated(sp<IBinderRpcTest> iface, size_t numCalls,
-                                        size_t sleepMs = 500) {
+static void testThreadPoolOverSaturated(sp<IBinderRpcTest> iface, size_t numCalls, size_t sleepMs) {
     size_t epochMsBefore = epochMillis();
 
     std::vector<std::thread> ts;
@@ -462,7 +464,9 @@
     constexpr size_t kNumThreads = 10;
     constexpr size_t kNumCalls = kNumThreads + 3;
     auto proc = createRpcTestSocketServerProcess({.numThreads = kNumThreads});
-    testThreadPoolOverSaturated(proc.rootIface, kNumCalls);
+
+    // b/272429574 - below 500ms, the test fails
+    testThreadPoolOverSaturated(proc.rootIface, kNumCalls, 500 /*ms*/);
 }
 
 TEST_P(BinderRpc, ThreadPoolLimitOutgoing) {
@@ -475,7 +479,9 @@
     constexpr size_t kNumCalls = kNumOutgoingConnections + 3;
     auto proc = createRpcTestSocketServerProcess(
             {.numThreads = kNumThreads, .numOutgoingConnections = kNumOutgoingConnections});
-    testThreadPoolOverSaturated(proc.rootIface, kNumCalls);
+
+    // b/272429574 - below 500ms, the test fails
+    testThreadPoolOverSaturated(proc.rootIface, kNumCalls, 500 /*ms*/);
 }
 
 TEST_P(BinderRpc, ThreadingStressTest) {
@@ -483,9 +489,9 @@
         GTEST_SKIP() << "This test requires multiple threads";
     }
 
-    constexpr size_t kNumClientThreads = 10;
-    constexpr size_t kNumServerThreads = 10;
-    constexpr size_t kNumCalls = 100;
+    constexpr size_t kNumClientThreads = 5;
+    constexpr size_t kNumServerThreads = 5;
+    constexpr size_t kNumCalls = 50;
 
     auto proc = createRpcTestSocketServerProcess({.numThreads = kNumServerThreads});
 
@@ -544,6 +550,8 @@
         GTEST_SKIP() << "This test requires multiple threads";
     }
 
+    constexpr size_t kNumServerThreads = 3;
+
     // This test forces a oneway transaction to be queued by issuing two
     // `blockingSendFdOneway` calls, then drains the queue by issuing two
     // `blockingRecvFd` calls.
@@ -552,7 +560,7 @@
     // https://developer.android.com/reference/android/os/IBinder#FLAG_ONEWAY
 
     auto proc = createRpcTestSocketServerProcess({
-            .numThreads = 3,
+            .numThreads = kNumServerThreads,
             .clientFileDescriptorTransportMode = RpcSession::FileDescriptorTransportMode::UNIX,
             .serverSupportedFileDescriptorTransportModes =
                     {RpcSession::FileDescriptorTransportMode::UNIX},
@@ -573,6 +581,8 @@
     EXPECT_OK(proc.rootIface->blockingRecvFd(&fdB));
     CHECK(android::base::ReadFdToString(fdB.get(), &result));
     EXPECT_EQ(result, "b");
+
+    saturateThreadPool(kNumServerThreads, proc.rootIface);
 }
 
 TEST_P(BinderRpc, OnewayCallQueueing) {
@@ -580,30 +590,22 @@
         GTEST_SKIP() << "This test requires multiple threads";
     }
 
-    constexpr size_t kNumSleeps = 10;
+    constexpr size_t kNumQueued = 10;
     constexpr size_t kNumExtraServerThreads = 4;
-    constexpr size_t kSleepMs = 50;
 
     // make sure calls to the same object happen on the same thread
     auto proc = createRpcTestSocketServerProcess({.numThreads = 1 + kNumExtraServerThreads});
 
-    EXPECT_OK(proc.rootIface->lock());
-
-    size_t epochMsBefore = epochMillis();
-
-    // all these *Async commands should be queued on the server sequentially,
+    // all these *Oneway commands should be queued on the server sequentially,
     // even though there are multiple threads.
-    for (size_t i = 0; i + 1 < kNumSleeps; i++) {
-        proc.rootIface->sleepMsAsync(kSleepMs);
+    for (size_t i = 0; i + 1 < kNumQueued; i++) {
+        proc.rootIface->blockingSendIntOneway(i);
     }
-    EXPECT_OK(proc.rootIface->unlockInMsAsync(kSleepMs));
-
-    // this can only return once the final async call has unlocked
-    EXPECT_OK(proc.rootIface->lockUnlock());
-
-    size_t epochMsAfter = epochMillis();
-
-    EXPECT_GE(epochMsAfter, epochMsBefore + kSleepMs * kNumSleeps);
+    for (size_t i = 0; i + 1 < kNumQueued; i++) {
+        int n;
+        proc.rootIface->blockingRecvInt(&n);
+        EXPECT_EQ(n, i);
+    }
 
     saturateThreadPool(1 + kNumExtraServerThreads, proc.rootIface);
 }
@@ -652,6 +654,32 @@
     proc.proc->sessions.erase(proc.proc->sessions.begin() + 1);
 }
 
+TEST_P(BinderRpc, SessionWithIncomingThreadpoolDoesntLeak) {
+    if (clientOrServerSingleThreaded()) {
+        GTEST_SKIP() << "This test requires multiple threads";
+    }
+
+    // session 0 - will check for leaks in destrutor of proc
+    // session 1 - we want to make sure it gets deleted when we drop all references to it
+    auto proc = createRpcTestSocketServerProcess(
+            {.numThreads = 1, .numIncomingConnectionsBySession = {0, 1}, .numSessions = 2});
+
+    wp<RpcSession> session = proc.proc->sessions.at(1).session;
+
+    // remove all references to the second session
+    proc.proc->sessions.at(1).root = nullptr;
+    proc.proc->sessions.erase(proc.proc->sessions.begin() + 1);
+
+    // TODO(b/271830568) more efficient way to wait for other incoming threadpool
+    // to drain commands.
+    for (size_t i = 0; i < 100; i++) {
+        usleep(10 * 1000);
+        if (session.promote() == nullptr) break;
+    }
+
+    EXPECT_EQ(nullptr, session.promote());
+}
+
 TEST_P(BinderRpc, SingleDeathRecipient) {
     if (clientOrServerSingleThreaded()) {
         GTEST_SKIP() << "This test requires multiple threads";
@@ -669,7 +697,7 @@
 
     // Death recipient needs to have an incoming connection to be called
     auto proc = createRpcTestSocketServerProcess(
-            {.numThreads = 1, .numSessions = 1, .numIncomingConnections = 1});
+            {.numThreads = 1, .numSessions = 1, .numIncomingConnectionsBySession = {1}});
 
     auto dr = sp<MyDeathRec>::make();
     ASSERT_EQ(OK, proc.rootBinder->linkToDeath(dr, (void*)1, 0));
@@ -682,6 +710,10 @@
     ASSERT_TRUE(dr->mCv.wait_for(lock, 100ms, [&]() { return dr->dead; }));
 
     // need to wait for the session to shutdown so we don't "Leak session"
+    // can't do this before checking the death recipient by calling
+    // forceShutdown earlier, because shutdownAndWait will also trigger
+    // a death recipient, but if we had a way to wait for the service
+    // to gracefully shutdown, we could use that here.
     EXPECT_TRUE(proc.proc->sessions.at(0).session->shutdownAndWait(true));
     proc.expectAlreadyShutdown = true;
 }
@@ -703,7 +735,7 @@
 
     // Death recipient needs to have an incoming connection to be called
     auto proc = createRpcTestSocketServerProcess(
-            {.numThreads = 1, .numSessions = 1, .numIncomingConnections = 1});
+            {.numThreads = 1, .numSessions = 1, .numIncomingConnectionsBySession = {1}});
 
     auto dr = sp<MyDeathRec>::make();
     EXPECT_EQ(OK, proc.rootBinder->linkToDeath(dr, (void*)1, 0));
@@ -736,8 +768,7 @@
         void binderDied(const wp<IBinder>& /* who */) override {}
     };
 
-    auto proc = createRpcTestSocketServerProcess(
-            {.numThreads = 1, .numSessions = 1, .numIncomingConnections = 0});
+    auto proc = createRpcTestSocketServerProcess({.numThreads = 1, .numSessions = 1});
 
     auto dr = sp<MyDeathRec>::make();
     EXPECT_EQ(INVALID_OPERATION, proc.rootBinder->linkToDeath(dr, (void*)1, 0));
@@ -756,19 +787,13 @@
 
     // Death recipient needs to have an incoming connection to be called
     auto proc = createRpcTestSocketServerProcess(
-            {.numThreads = 1, .numSessions = 1, .numIncomingConnections = 1});
+            {.numThreads = 1, .numSessions = 1, .numIncomingConnectionsBySession = {1}});
 
     auto dr = sp<MyDeathRec>::make();
     ASSERT_EQ(OK, proc.rootBinder->linkToDeath(dr, (void*)1, 0));
     ASSERT_EQ(OK, proc.rootBinder->unlinkToDeath(dr, (void*)1, 0, nullptr));
 
-    if (auto status = proc.rootIface->scheduleShutdown(); !status.isOk()) {
-        EXPECT_EQ(DEAD_OBJECT, status.transactionError()) << status;
-    }
-
-    // need to wait for the session to shutdown so we don't "Leak session"
-    EXPECT_TRUE(proc.proc->sessions.at(0).session->shutdownAndWait(true));
-    proc.expectAlreadyShutdown = true;
+    proc.forceShutdown();
 }
 
 TEST_P(BinderRpc, Die) {
@@ -1223,7 +1248,7 @@
     };
 
     auto [isStrong1, isStrong2, rpcSecurity] = GetParam();
-    auto server = RpcServer::make(newFactory(rpcSecurity));
+    auto server = RpcServer::make(newTlsFactory(rpcSecurity));
     auto binder1 = sp<BBinder>::make();
     IBinder* binderRaw1 = binder1.get();
     setRootObject(isStrong1)(server.get(), binder1);
@@ -1319,7 +1344,7 @@
 class BinderRpcServerOnly : public ::testing::TestWithParam<std::tuple<RpcSecurity, uint32_t>> {
 public:
     static std::string PrintTestParam(const ::testing::TestParamInfo<ParamType>& info) {
-        return std::string(newFactory(std::get<0>(info.param))->toCString()) + "_serverV" +
+        return std::string(newTlsFactory(std::get<0>(info.param))->toCString()) + "_serverV" +
                 std::to_string(std::get<1>(info.param));
     }
 };
@@ -1327,7 +1352,7 @@
 TEST_P(BinderRpcServerOnly, SetExternalServerTest) {
     base::unique_fd sink(TEMP_FAILURE_RETRY(open("/dev/null", O_RDWR)));
     int sinkFd = sink.get();
-    auto server = RpcServer::make(newFactory(std::get<0>(GetParam())));
+    auto server = RpcServer::make(newTlsFactory(std::get<0>(GetParam())));
     server->setProtocolVersion(std::get<1>(GetParam()));
     ASSERT_FALSE(server->hasServer());
     ASSERT_EQ(OK, server->setupExternalServer(std::move(sink)));
@@ -1343,7 +1368,7 @@
     }
 
     auto addr = allocateSocketAddress();
-    auto server = RpcServer::make(newFactory(std::get<0>(GetParam())));
+    auto server = RpcServer::make(newTlsFactory(std::get<0>(GetParam())));
     server->setProtocolVersion(std::get<1>(GetParam()));
     ASSERT_EQ(OK, server->setupUnixDomainServer(addr.c_str()));
     auto joinEnds = std::make_shared<OneOffSignal>();
@@ -1392,7 +1417,7 @@
                 const Param& param,
                 std::unique_ptr<RpcAuth> auth = std::make_unique<RpcAuthSelfSigned>()) {
             auto [socketType, rpcSecurity, certificateFormat, serverVersion] = param;
-            auto rpcServer = RpcServer::make(newFactory(rpcSecurity));
+            auto rpcServer = RpcServer::make(newTlsFactory(rpcSecurity));
             rpcServer->setProtocolVersion(serverVersion);
             switch (socketType) {
                 case SocketType::PRECONNECTED: {
@@ -1471,7 +1496,7 @@
             }
             mFd = rpcServer->releaseServer();
             if (!mFd.fd.ok()) return AssertionFailure() << "releaseServer returns invalid fd";
-            mCtx = newFactory(rpcSecurity, mCertVerifier, std::move(auth))->newServerCtx();
+            mCtx = newTlsFactory(rpcSecurity, mCertVerifier, std::move(auth))->newServerCtx();
             if (mCtx == nullptr) return AssertionFailure() << "newServerCtx";
             mSetup = true;
             return AssertionSuccess();
@@ -1576,7 +1601,7 @@
             auto [socketType, rpcSecurity, certificateFormat, serverVersion] = param;
             (void)serverVersion;
             mFdTrigger = FdTrigger::make();
-            mCtx = newFactory(rpcSecurity, mCertVerifier)->newClientCtx();
+            mCtx = newTlsFactory(rpcSecurity, mCertVerifier)->newClientCtx();
             if (mCtx == nullptr) return AssertionFailure() << "newClientCtx";
             return AssertionSuccess();
         }
@@ -1648,7 +1673,7 @@
     using Client = RpcTransportTestUtils::Client;
     static inline std::string PrintParamInfo(const testing::TestParamInfo<ParamType>& info) {
         auto [socketType, rpcSecurity, certificateFormat, serverVersion] = info.param;
-        auto ret = PrintToString(socketType) + "_" + newFactory(rpcSecurity)->toCString();
+        auto ret = PrintToString(socketType) + "_" + newTlsFactory(rpcSecurity)->toCString();
         if (certificateFormat.has_value()) ret += "_" + PrintToString(*certificateFormat);
         ret += "_serverV" + std::to_string(serverVersion);
         return ret;
diff --git a/libs/binder/tests/binderRpcTestCommon.h b/libs/binder/tests/binderRpcTestCommon.h
index a467ee3..c1364dd 100644
--- a/libs/binder/tests/binderRpcTestCommon.h
+++ b/libs/binder/tests/binderRpcTestCommon.h
@@ -126,7 +126,11 @@
 struct BinderRpcOptions {
     size_t numThreads = 1;
     size_t numSessions = 1;
-    size_t numIncomingConnections = 0;
+    // right now, this can be empty, or length numSessions, where each value
+    // represents the info for the corresponding session, but we should
+    // probably switch this to be a list of sessions options so that other
+    // options can all be specified per session
+    std::vector<size_t> numIncomingConnectionsBySession = {};
     size_t numOutgoingConnections = SIZE_MAX;
     RpcSession::FileDescriptorTransportMode clientFileDescriptorTransportMode =
             RpcSession::FileDescriptorTransportMode::NONE;
@@ -170,7 +174,7 @@
     return object;
 }
 
-static inline std::unique_ptr<RpcTransportCtxFactory> newFactory(
+static inline std::unique_ptr<RpcTransportCtxFactory> newTlsFactory(
         RpcSecurity rpcSecurity, std::shared_ptr<RpcCertificateVerifier> verifier = nullptr,
         std::unique_ptr<RpcAuth> auth = nullptr) {
     switch (rpcSecurity) {
@@ -445,6 +449,12 @@
     Status blockingRecvFd(android::os::ParcelFileDescriptor* /*fd*/) override {
         return Status::fromStatusT(UNKNOWN_TRANSACTION);
     }
+
+    Status blockingSendIntOneway(int /*n*/) override {
+        return Status::fromStatusT(UNKNOWN_TRANSACTION);
+    }
+
+    Status blockingRecvInt(int* /*n*/) override { return Status::fromStatusT(UNKNOWN_TRANSACTION); }
 };
 
 } // namespace android
diff --git a/libs/binder/tests/binderRpcTestFixture.h b/libs/binder/tests/binderRpcTestFixture.h
index c99d68a..6cde9f7 100644
--- a/libs/binder/tests/binderRpcTestFixture.h
+++ b/libs/binder/tests/binderRpcTestFixture.h
@@ -64,6 +64,21 @@
     // whether session should be invalidated by end of run
     bool expectAlreadyShutdown = false;
 
+    // TODO(b/271830568): fix this in binderRpcTest, we always use the first session to cause the
+    // remote process to shutdown. Normally, when we shutdown, the default in the destructor is to
+    // check that there are no leaks and shutdown. However, when there are incoming threadpools,
+    // there will be a few extra binder threads there, so we can't shutdown the server. We should
+    // consider an alternative way of doing the test so that we don't need this, some ideas, such as
+    // program in understanding of incoming threadpool into the destructor so that (e.g.
+    // intelligently wait for sessions to shutdown now that they will do this)
+    void forceShutdown() {
+        if (auto status = rootIface->scheduleShutdown(); !status.isOk()) {
+            EXPECT_EQ(DEAD_OBJECT, status.transactionError()) << status;
+        }
+        EXPECT_TRUE(proc->sessions.at(0).session->shutdownAndWait(true));
+        expectAlreadyShutdown = true;
+    }
+
     BinderRpcTestProcessSession(BinderRpcTestProcessSession&&) = default;
     ~BinderRpcTestProcessSession() {
         if (!expectAlreadyShutdown) {
@@ -133,9 +148,26 @@
         return ret;
     }
 
-    static std::string PrintParamInfo(const testing::TestParamInfo<ParamType>& info);
+    static std::string PrintParamInfo(const testing::TestParamInfo<ParamType>& info) {
+        auto [type, security, clientVersion, serverVersion, singleThreaded, noKernel] = info.param;
+        auto ret = PrintToString(type) + "_" + newFactory(security)->toCString() + "_clientV" +
+                std::to_string(clientVersion) + "_serverV" + std::to_string(serverVersion);
+        if (singleThreaded) {
+            ret += "_single_threaded";
+        } else {
+            ret += "_multi_threaded";
+        }
+        if (noKernel) {
+            ret += "_no_kernel";
+        } else {
+            ret += "_with_kernel";
+        }
+        return ret;
+    }
 
 protected:
+    static std::unique_ptr<RpcTransportCtxFactory> newFactory(RpcSecurity rpcSecurity);
+
     std::unique_ptr<ProcessSession> createRpcTestSocketServerProcessEtc(
             const BinderRpcOptions& options);
 };
diff --git a/libs/binder/tests/binderRpcTestService.cpp b/libs/binder/tests/binderRpcTestService.cpp
index a27bd2f..a9736d5 100644
--- a/libs/binder/tests/binderRpcTestService.cpp
+++ b/libs/binder/tests/binderRpcTestService.cpp
@@ -83,6 +83,18 @@
         fd->reset(mFdChannel.read());
         return Status::ok();
     }
+
+    HandoffChannel<int> mIntChannel;
+
+    Status blockingSendIntOneway(int n) override {
+        mIntChannel.write(n);
+        return Status::ok();
+    }
+
+    Status blockingRecvInt(int* n) override {
+        *n = mIntChannel.read();
+        return Status::ok();
+    }
 };
 
 int main(int argc, char* argv[]) {
@@ -104,7 +116,7 @@
     }
 
     auto certVerifier = std::make_shared<RpcCertificateVerifierSimple>();
-    sp<RpcServer> server = RpcServer::make(newFactory(rpcSecurity, certVerifier));
+    sp<RpcServer> server = RpcServer::make(newTlsFactory(rpcSecurity, certVerifier));
 
     server->setProtocolVersion(serverConfig.serverVersion);
     server->setMaxThreads(serverConfig.numThreads);
diff --git a/libs/binder/tests/binderRpcTestTrusty.cpp b/libs/binder/tests/binderRpcTestTrusty.cpp
index b3bb5eb..28be10d 100644
--- a/libs/binder/tests/binderRpcTestTrusty.cpp
+++ b/libs/binder/tests/binderRpcTestTrusty.cpp
@@ -39,27 +39,25 @@
     void terminate() override { LOG_ALWAYS_FATAL("terminate() not supported"); }
 };
 
-std::string BinderRpc::PrintParamInfo(const testing::TestParamInfo<ParamType>& info) {
-    auto [type, security, clientVersion, serverVersion, singleThreaded, noKernel] = info.param;
-    auto ret = PrintToString(type) + "_clientV" + std::to_string(clientVersion) + "_serverV" +
-            std::to_string(serverVersion);
-    if (singleThreaded) {
-        ret += "_single_threaded";
+std::unique_ptr<RpcTransportCtxFactory> BinderRpc::newFactory(RpcSecurity rpcSecurity) {
+    switch (rpcSecurity) {
+        case RpcSecurity::RAW:
+            return RpcTransportCtxFactoryTipcTrusty::make();
+        default:
+            LOG_ALWAYS_FATAL("Unknown RpcSecurity %d", rpcSecurity);
     }
-    if (noKernel) {
-        ret += "_no_kernel";
-    }
-    return ret;
 }
 
 // This creates a new process serving an interface on a certain number of
 // threads.
 std::unique_ptr<ProcessSession> BinderRpc::createRpcTestSocketServerProcessEtc(
         const BinderRpcOptions& options) {
-    LOG_ALWAYS_FATAL_IF(options.numIncomingConnections != 0,
-                        "Non-zero incoming connections %zu on Trusty",
-                        options.numIncomingConnections);
+    LOG_ALWAYS_FATAL_IF(std::any_of(options.numIncomingConnectionsBySession.begin(),
+                                    options.numIncomingConnectionsBySession.end(),
+                                    [](size_t n) { return n != 0; }),
+                        "Non-zero incoming connections on Trusty");
 
+    RpcSecurity rpcSecurity = std::get<1>(GetParam());
     uint32_t clientVersion = std::get<2>(GetParam());
     uint32_t serverVersion = std::get<3>(GetParam());
 
@@ -67,11 +65,10 @@
 
     status_t status;
     for (size_t i = 0; i < options.numSessions; i++) {
-        auto factory = android::RpcTransportCtxFactoryTipcTrusty::make();
-        auto session = android::RpcSession::make(std::move(factory));
+        auto session = android::RpcSession::make(newFactory(rpcSecurity));
 
         EXPECT_TRUE(session->setProtocolVersion(clientVersion));
-        session->setMaxOutgoingThreads(options.numOutgoingConnections);
+        session->setMaxOutgoingConnections(options.numOutgoingConnections);
         session->setFileDescriptorTransportMode(options.clientFileDescriptorTransportMode);
 
         status = session->setupPreconnectedClient({}, [&]() {
diff --git a/libs/binder/tests/binderRpcUniversalTests.cpp b/libs/binder/tests/binderRpcUniversalTests.cpp
index 11a22b0..1f46010 100644
--- a/libs/binder/tests/binderRpcUniversalTests.cpp
+++ b/libs/binder/tests/binderRpcUniversalTests.cpp
@@ -463,7 +463,7 @@
                 auto proc = createRpcTestSocketServerProcess(
                         {.numThreads = 1,
                          .numSessions = 1,
-                         .numIncomingConnections = numIncomingConnections});
+                         .numIncomingConnectionsBySession = {numIncomingConnections}});
                 auto cb = sp<MyBinderRpcCallback>::make();
 
                 if (callIsOneway) {
@@ -491,16 +491,7 @@
                         << "callIsOneway: " << callIsOneway
                         << " callbackIsOneway: " << callbackIsOneway << " delayed: " << delayed;
 
-                // since we are severing the connection, we need to go ahead and
-                // tell the server to shutdown and exit so that waitpid won't hang
-                if (auto status = proc.rootIface->scheduleShutdown(); !status.isOk()) {
-                    EXPECT_EQ(DEAD_OBJECT, status.transactionError()) << status;
-                }
-
-                // since this session has an incoming connection w/ a threadpool, we
-                // need to manually shut it down
-                EXPECT_TRUE(proc.proc->sessions.at(0).session->shutdownAndWait(true));
-                proc.expectAlreadyShutdown = true;
+                proc.forceShutdown();
             }
         }
     }
diff --git a/libs/binder/tests/unit_fuzzers/Android.bp b/libs/binder/tests/unit_fuzzers/Android.bp
index 8ea948c..a881582 100644
--- a/libs/binder/tests/unit_fuzzers/Android.bp
+++ b/libs/binder/tests/unit_fuzzers/Android.bp
@@ -104,3 +104,42 @@
     defaults: ["binder_fuzz_defaults"],
     srcs: ["MemoryDealerFuzz.cpp"],
 }
+
+cc_fuzz {
+    name: "binder_recordedTransactionFileFuzz",
+    defaults: ["binder_fuzz_defaults"],
+    srcs: ["RecordedTransactionFileFuzz.cpp"],
+    corpus: [
+        "recorded_transaction_corpus/*",
+    ],
+}
+
+cc_fuzz {
+    name: "binder_recordedTransactionFuzz",
+    defaults: ["binder_fuzz_defaults"],
+    srcs: ["RecordedTransactionFuzz.cpp"],
+    target: {
+        android: {
+            shared_libs: [
+                "libcutils",
+                "libutils",
+                "libbase",
+                "libbinder",
+            ],
+            static_libs: ["libbinder_random_parcel"],
+        },
+        host: {
+            static_libs: [
+                "libcutils",
+                "liblog",
+                "libutils",
+                "libbase",
+                "libbinder",
+                "libbinder_random_parcel",
+            ],
+        },
+        darwin: {
+            enabled: false,
+        },
+    },
+}
diff --git a/libs/binder/tests/unit_fuzzers/RecordedTransactionFileFuzz.cpp b/libs/binder/tests/unit_fuzzers/RecordedTransactionFileFuzz.cpp
new file mode 100644
index 0000000..e494366
--- /dev/null
+++ b/libs/binder/tests/unit_fuzzers/RecordedTransactionFileFuzz.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/macros.h>
+#include <binder/RecordedTransaction.h>
+#include <filesystem>
+
+#include "fuzzer/FuzzedDataProvider.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    std::FILE* intermediateFile = std::tmpfile();
+    fwrite(data, sizeof(uint8_t), size, intermediateFile);
+    rewind(intermediateFile);
+    int fileNumber = fileno(intermediateFile);
+
+    android::base::unique_fd fd(dup(fileNumber));
+
+    auto transaction = android::binder::debug::RecordedTransaction::fromFile(fd);
+
+    std::fclose(intermediateFile);
+
+    if (transaction.has_value()) {
+        intermediateFile = std::tmpfile();
+
+        android::base::unique_fd fdForWriting(fileno(intermediateFile));
+        auto writeStatus ATTRIBUTE_UNUSED = transaction.value().dumpToFile(fdForWriting);
+
+        std::fclose(intermediateFile);
+    }
+
+    return 0;
+}
diff --git a/libs/binder/tests/unit_fuzzers/RecordedTransactionFuzz.cpp b/libs/binder/tests/unit_fuzzers/RecordedTransactionFuzz.cpp
new file mode 100644
index 0000000..943fb9f
--- /dev/null
+++ b/libs/binder/tests/unit_fuzzers/RecordedTransactionFuzz.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/macros.h>
+#include <binder/RecordedTransaction.h>
+#include <fuzzbinder/random_parcel.h>
+#include <filesystem>
+#include <string>
+
+#include "fuzzer/FuzzedDataProvider.h"
+
+using android::fillRandomParcel;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    FuzzedDataProvider provider = FuzzedDataProvider(data, size);
+
+    android::String16 interfaceName =
+            android::String16(provider.ConsumeRandomLengthString().c_str());
+
+    uint32_t code = provider.ConsumeIntegral<uint32_t>();
+    uint32_t flags = provider.ConsumeIntegral<uint32_t>();
+    time_t sec = provider.ConsumeIntegral<time_t>();
+    long nsec = provider.ConsumeIntegral<long>();
+    timespec timestamp = {.tv_sec = sec, .tv_nsec = nsec};
+    android::status_t transactionStatus = provider.ConsumeIntegral<android::status_t>();
+
+    std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>(
+            provider.ConsumeIntegralInRange<size_t>(0, provider.remaining_bytes()));
+
+    // same options so that FDs and binders could be shared in both Parcels
+    android::RandomParcelOptions options;
+
+    android::Parcel p0, p1;
+    fillRandomParcel(&p0, FuzzedDataProvider(bytes.data(), bytes.size()), &options);
+    fillRandomParcel(&p1, std::move(provider), &options);
+
+    auto transaction =
+            android::binder::debug::RecordedTransaction::fromDetails(interfaceName, code, flags,
+                                                                     timestamp, p0, p1,
+                                                                     transactionStatus);
+
+    if (transaction.has_value()) {
+        std::FILE* intermediateFile = std::tmpfile();
+        android::base::unique_fd fdForWriting(fileno(intermediateFile));
+        auto writeStatus ATTRIBUTE_UNUSED = transaction.value().dumpToFile(fdForWriting);
+
+        std::fclose(intermediateFile);
+    }
+
+    return 0;
+}
diff --git a/libs/binder/tests/unit_fuzzers/recorded_transaction_corpus/power_recording b/libs/binder/tests/unit_fuzzers/recorded_transaction_corpus/power_recording
new file mode 100644
index 0000000..79442078
--- /dev/null
+++ b/libs/binder/tests/unit_fuzzers/recorded_transaction_corpus/power_recording
Binary files differ
diff --git a/libs/binder/tests/unit_fuzzers/recorded_transaction_corpus/recorded_binder_transaction b/libs/binder/tests/unit_fuzzers/recorded_transaction_corpus/recorded_binder_transaction
new file mode 100644
index 0000000..658addb
--- /dev/null
+++ b/libs/binder/tests/unit_fuzzers/recorded_transaction_corpus/recorded_binder_transaction
Binary files differ
diff --git a/libs/binderdebug/Android.bp b/libs/binderdebug/Android.bp
index 3eeaf3e..1454727 100644
--- a/libs/binderdebug/Android.bp
+++ b/libs/binderdebug/Android.bp
@@ -21,6 +21,17 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
+cc_benchmark {
+    name: "binder_thread_stats",
+    shared_libs: [
+        "libutils",
+        "libbinder",
+        "libbase",
+    ],
+    static_libs: ["libbinderdebug"],
+    srcs: ["stats.cpp"],
+}
+
 cc_library {
     name: "libbinderdebug",
     vendor_available: true,
diff --git a/libs/binderdebug/stats.cpp b/libs/binderdebug/stats.cpp
new file mode 100644
index 0000000..9c26afa
--- /dev/null
+++ b/libs/binderdebug/stats.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <binder/BpBinder.h>
+#include <binder/IServiceManager.h>
+#include <binderdebug/BinderDebug.h>
+#include <utils/Errors.h>
+
+#include <inttypes.h>
+
+namespace android {
+
+extern "C" int main() {
+    // ignore args - we only print csv
+
+    // we should use a csv library here for escaping, because
+    // the name is coming from another process
+    printf("name,binder_threads_in_use,binder_threads_started,client_count\n");
+
+    for (const String16& name : defaultServiceManager()->listServices()) {
+        sp<IBinder> binder = defaultServiceManager()->checkService(name);
+        if (binder == nullptr) {
+            fprintf(stderr, "%s is null", String8(name).c_str());
+            continue;
+        }
+
+        BpBinder* remote = binder->remoteBinder();
+        const auto handle = remote->getDebugBinderHandle();
+        CHECK(handle != std::nullopt);
+
+        pid_t pid;
+        CHECK_EQ(OK, binder->getDebugPid(&pid));
+
+        BinderPidInfo info;
+        CHECK_EQ(OK, getBinderPidInfo(BinderDebugContext::BINDER, pid, &info));
+
+        std::vector<pid_t> clientPids;
+        CHECK_EQ(OK,
+                 getBinderClientPids(BinderDebugContext::BINDER, getpid(), pid, *handle,
+                                     &clientPids));
+
+        printf("%s,%" PRIu32 ",%" PRIu32 ",%zu\n", String8(name).c_str(), info.threadUsage,
+               info.threadCount, clientPids.size());
+    }
+    return 0;
+}
+
+} // namespace android
diff --git a/libs/fakeservicemanager/Android.bp b/libs/fakeservicemanager/Android.bp
index 29924ff..96dcce1 100644
--- a/libs/fakeservicemanager/Android.bp
+++ b/libs/fakeservicemanager/Android.bp
@@ -11,7 +11,7 @@
     name: "fakeservicemanager_defaults",
     host_supported: true,
     srcs: [
-        "ServiceManager.cpp",
+        "FakeServiceManager.cpp",
     ],
 
     shared_libs: [
@@ -28,7 +28,7 @@
 cc_library {
     name: "libfakeservicemanager",
     defaults: ["fakeservicemanager_defaults"],
-    export_include_dirs: ["include/fakeservicemanager"],
+    export_include_dirs: ["include"],
 }
 
 cc_test_host {
@@ -38,5 +38,5 @@
         "test_sm.cpp",
     ],
     static_libs: ["libgmock"],
-    local_include_dirs: ["include/fakeservicemanager"],
+    local_include_dirs: ["include"],
 }
diff --git a/libs/fakeservicemanager/ServiceManager.cpp b/libs/fakeservicemanager/FakeServiceManager.cpp
similarity index 66%
rename from libs/fakeservicemanager/ServiceManager.cpp
rename to libs/fakeservicemanager/FakeServiceManager.cpp
index 1109ad8..3272bbc 100644
--- a/libs/fakeservicemanager/ServiceManager.cpp
+++ b/libs/fakeservicemanager/FakeServiceManager.cpp
@@ -14,18 +14,18 @@
  * limitations under the License.
  */
 
-#include "ServiceManager.h"
+#include "fakeservicemanager/FakeServiceManager.h"
 
 namespace android {
 
-ServiceManager::ServiceManager() {}
+FakeServiceManager::FakeServiceManager() {}
 
-sp<IBinder> ServiceManager::getService( const String16& name) const {
+sp<IBinder> FakeServiceManager::getService( const String16& name) const {
     // Servicemanager is single-threaded and cannot block. This method exists for legacy reasons.
     return checkService(name);
 }
 
-sp<IBinder> ServiceManager::checkService( const String16& name) const {
+sp<IBinder> FakeServiceManager::checkService( const String16& name) const {
     auto it = mNameToService.find(name);
     if (it == mNameToService.end()) {
         return nullptr;
@@ -33,7 +33,7 @@
     return it->second;
 }
 
-status_t ServiceManager::addService(const String16& name, const sp<IBinder>& service,
+status_t FakeServiceManager::addService(const String16& name, const sp<IBinder>& service,
                                 bool /*allowIsolated*/,
                                 int /*dumpsysFlags*/) {
     if (service == nullptr) {
@@ -43,7 +43,7 @@
     return NO_ERROR;
 }
 
-Vector<String16> ServiceManager::listServices(int /*dumpsysFlags*/) {
+Vector<String16> FakeServiceManager::listServices(int /*dumpsysFlags*/) {
     Vector<String16> services;
     for (auto const& [name, service] : mNameToService) {
         (void) service;
@@ -52,19 +52,19 @@
   return services;
 }
 
-IBinder* ServiceManager::onAsBinder() {
+IBinder* FakeServiceManager::onAsBinder() {
     return nullptr;
 }
 
-sp<IBinder> ServiceManager::waitForService(const String16& name) {
+sp<IBinder> FakeServiceManager::waitForService(const String16& name) {
     return checkService(name);
 }
 
-bool ServiceManager::isDeclared(const String16& name) {
+bool FakeServiceManager::isDeclared(const String16& name) {
     return mNameToService.find(name) != mNameToService.end();
 }
 
-Vector<String16> ServiceManager::getDeclaredInstances(const String16& name) {
+Vector<String16> FakeServiceManager::getDeclaredInstances(const String16& name) {
     Vector<String16> out;
     const String16 prefix = name + String16("/");
     for (const auto& [registeredName, service] : mNameToService) {
@@ -76,38 +76,38 @@
     return out;
 }
 
-std::optional<String16> ServiceManager::updatableViaApex(const String16& name) {
+std::optional<String16> FakeServiceManager::updatableViaApex(const String16& name) {
     (void)name;
     return std::nullopt;
 }
 
-Vector<String16> ServiceManager::getUpdatableNames(const String16& apexName) {
+Vector<String16> FakeServiceManager::getUpdatableNames(const String16& apexName) {
     (void)apexName;
     return {};
 }
 
-std::optional<IServiceManager::ConnectionInfo> ServiceManager::getConnectionInfo(
+std::optional<IServiceManager::ConnectionInfo> FakeServiceManager::getConnectionInfo(
         const String16& name) {
     (void)name;
     return std::nullopt;
 }
 
-status_t ServiceManager::registerForNotifications(const String16&,
+status_t FakeServiceManager::registerForNotifications(const String16&,
                                                   const sp<LocalRegistrationCallback>&) {
     return INVALID_OPERATION;
 }
 
-status_t ServiceManager::unregisterForNotifications(const String16&,
+status_t FakeServiceManager::unregisterForNotifications(const String16&,
                                                 const sp<LocalRegistrationCallback>&) {
     return INVALID_OPERATION;
 }
 
-std::vector<IServiceManager::ServiceDebugInfo> ServiceManager::getServiceDebugInfo() {
+std::vector<IServiceManager::ServiceDebugInfo> FakeServiceManager::getServiceDebugInfo() {
     std::vector<IServiceManager::ServiceDebugInfo> ret;
     return ret;
 }
 
-void ServiceManager::clear() {
+void FakeServiceManager::clear() {
     mNameToService.clear();
 }
 }  // namespace android
diff --git a/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h b/libs/fakeservicemanager/include/fakeservicemanager/FakeServiceManager.h
similarity index 96%
rename from libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h
rename to libs/fakeservicemanager/include/fakeservicemanager/FakeServiceManager.h
index ba6bb7d..97add24 100644
--- a/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h
+++ b/libs/fakeservicemanager/include/fakeservicemanager/FakeServiceManager.h
@@ -28,9 +28,9 @@
  * A local host simple implementation of IServiceManager, that does not
  * communicate over binder.
 */
-class ServiceManager : public IServiceManager {
+class FakeServiceManager : public IServiceManager {
 public:
-    ServiceManager();
+    FakeServiceManager();
 
     sp<IBinder> getService( const String16& name) const override;
 
diff --git a/libs/fakeservicemanager/test_sm.cpp b/libs/fakeservicemanager/test_sm.cpp
index 8682c1c..6fc21c6 100644
--- a/libs/fakeservicemanager/test_sm.cpp
+++ b/libs/fakeservicemanager/test_sm.cpp
@@ -21,14 +21,14 @@
 #include <binder/ProcessState.h>
 #include <binder/IServiceManager.h>
 
-#include "ServiceManager.h"
+#include "fakeservicemanager/FakeServiceManager.h"
 
 using android::sp;
 using android::BBinder;
 using android::IBinder;
 using android::OK;
 using android::status_t;
-using android::ServiceManager;
+using android::FakeServiceManager;
 using android::String16;
 using android::IServiceManager;
 using testing::ElementsAre;
@@ -45,19 +45,19 @@
 }
 
 TEST(AddService, HappyHappy) {
-    auto sm = new ServiceManager();
+    auto sm = new FakeServiceManager();
     EXPECT_EQ(sm->addService(String16("foo"), getBinder(), false /*allowIsolated*/,
         IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), OK);
 }
 
 TEST(AddService, SadNullBinder) {
-    auto sm = new ServiceManager();
+    auto sm = new FakeServiceManager();
     EXPECT_EQ(sm->addService(String16("foo"), nullptr, false /*allowIsolated*/,
         IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), android::UNEXPECTED_NULL);
 }
 
 TEST(AddService, HappyOverExistingService) {
-    auto sm = new ServiceManager();
+    auto sm = new FakeServiceManager();
     EXPECT_EQ(sm->addService(String16("foo"), getBinder(), false /*allowIsolated*/,
         IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), OK);
     EXPECT_EQ(sm->addService(String16("foo"), getBinder(), false /*allowIsolated*/,
@@ -65,7 +65,7 @@
 }
 
 TEST(AddService, HappyClearAddedService) {
-    auto sm = new ServiceManager();
+    auto sm = new FakeServiceManager();
     EXPECT_EQ(sm->addService(String16("foo"), getBinder(), false /*allowIsolated*/,
         IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), OK);
     EXPECT_NE(sm->getService(String16("foo")), nullptr);
@@ -74,7 +74,7 @@
 }
 
 TEST(GetService, HappyHappy) {
-    auto sm = new ServiceManager();
+    auto sm = new FakeServiceManager();
     sp<IBinder> service = getBinder();
 
     EXPECT_EQ(sm->addService(String16("foo"), service, false /*allowIsolated*/,
@@ -84,13 +84,13 @@
 }
 
 TEST(GetService, NonExistant) {
-    auto sm = new ServiceManager();
+    auto sm = new FakeServiceManager();
 
     EXPECT_EQ(sm->getService(String16("foo")), nullptr);
 }
 
 TEST(ListServices, AllServices) {
-    auto sm = new ServiceManager();
+    auto sm = new FakeServiceManager();
 
     EXPECT_EQ(sm->addService(String16("sd"), getBinder(), false /*allowIsolated*/,
         IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), OK);
@@ -109,13 +109,13 @@
 }
 
 TEST(WaitForService, NonExistant) {
-    auto sm = new ServiceManager();
+    auto sm = new FakeServiceManager();
 
     EXPECT_EQ(sm->waitForService(String16("foo")), nullptr);
 }
 
 TEST(WaitForService, HappyHappy) {
-    auto sm = new ServiceManager();
+    auto sm = new FakeServiceManager();
     sp<IBinder> service = getBinder();
 
     EXPECT_EQ(sm->addService(String16("foo"), service, false /*allowIsolated*/,
@@ -125,13 +125,13 @@
 }
 
 TEST(IsDeclared, NonExistant) {
-    auto sm = new ServiceManager();
+    auto sm = new FakeServiceManager();
 
     EXPECT_FALSE(sm->isDeclared(String16("foo")));
 }
 
 TEST(IsDeclared, HappyHappy) {
-    auto sm = new ServiceManager();
+    auto sm = new FakeServiceManager();
     sp<IBinder> service = getBinder();
 
     EXPECT_EQ(sm->addService(String16("foo"), service, false /*allowIsolated*/,
diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp
index 8e57152..ea1b5e4 100644
--- a/libs/ftl/Android.bp
+++ b/libs/ftl/Android.bp
@@ -10,9 +10,6 @@
 cc_test {
     name: "ftl_test",
     test_suites: ["device-tests"],
-    sanitize: {
-        address: true,
-    },
     srcs: [
         "algorithm_test.cpp",
         "cast_test.cpp",
diff --git a/libs/ftl/TEST_MAPPING b/libs/ftl/TEST_MAPPING
new file mode 100644
index 0000000..ec0c671
--- /dev/null
+++ b/libs/ftl/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "ftl_test"
+    }
+  ],
+  "hwasan-presubmit": [
+    {
+      "name": "ftl_test"
+    }
+  ]
+}
diff --git a/libs/ftl/optional_test.cpp b/libs/ftl/optional_test.cpp
index 6b3b6c4..91bf7bc 100644
--- a/libs/ftl/optional_test.cpp
+++ b/libs/ftl/optional_test.cpp
@@ -164,6 +164,46 @@
                      }));
 }
 
+TEST(Optional, OrElse) {
+  // Non-empty.
+  {
+    const Optional opt = false;
+    EXPECT_EQ(false, opt.or_else([] { return Optional(true); }));
+    EXPECT_EQ('x', Optional('x').or_else([] { return std::make_optional('y'); }));
+  }
+
+  // Empty.
+  {
+    const Optional<int> opt;
+    EXPECT_EQ(123, opt.or_else([]() -> Optional<int> { return 123; }));
+    EXPECT_EQ("abc"s, Optional<std::string>().or_else([] { return Optional("abc"s); }));
+  }
+  {
+    bool empty = false;
+    EXPECT_EQ(Optional<float>(), Optional<float>().or_else([&empty]() -> Optional<float> {
+      empty = true;
+      return std::nullopt;
+    }));
+    EXPECT_TRUE(empty);
+  }
+
+  // Chaining.
+  using StringVector = StaticVector<std::string, 3>;
+  EXPECT_EQ(999, Optional(StaticVector{"1"s, "0"s, "0"s})
+                     .and_then([](StringVector&& v) -> Optional<StringVector> {
+                       if (v.push_back("0"s)) return v;
+                       return {};
+                     })
+                     .or_else([] {
+                       return Optional(StaticVector{"9"s, "9"s, "9"s});
+                     })
+                     .transform([](const StringVector& v) {
+                       return std::accumulate(v.begin(), v.end(), std::string());
+                     })
+                     .and_then(parse_int)
+                     .or_else([] { return Optional(-1); }));
+}
+
 // Comparison.
 namespace {
 
diff --git a/libs/graphicsenv/OWNERS b/libs/graphicsenv/OWNERS
index 347c4e0..1db8cbe 100644
--- a/libs/graphicsenv/OWNERS
+++ b/libs/graphicsenv/OWNERS
@@ -1,10 +1,4 @@
-abdolrashidi@google.com
-cclao@google.com
 chrisforbes@google.com
 cnorthrop@google.com
 ianelliott@google.com
-lfy@google.com
 lpy@google.com
-romanl@google.com
-vantablack@google.com
-yuxinhu@google.com
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index cf8b13f..821dd37 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -139,6 +139,11 @@
     }
 }
 
+void BLASTBufferItemConsumer::resizeFrameEventHistory(size_t newSize) {
+    Mutex::Autolock lock(mMutex);
+    mFrameEventHistory.resize(newSize);
+}
+
 BLASTBufferQueue::BLASTBufferQueue(const std::string& name, bool updateDestinationFrame)
       : mSurfaceControl(nullptr),
         mSize(1, 1),
@@ -1069,8 +1074,9 @@
 // can be non-blocking when the producer is in the client process.
 class BBQBufferQueueProducer : public BufferQueueProducer {
 public:
-    BBQBufferQueueProducer(const sp<BufferQueueCore>& core)
-          : BufferQueueProducer(core, false /* consumerIsSurfaceFlinger*/) {}
+    BBQBufferQueueProducer(const sp<BufferQueueCore>& core, wp<BLASTBufferQueue> bbq)
+          : BufferQueueProducer(core, false /* consumerIsSurfaceFlinger*/),
+            mBLASTBufferQueue(std::move(bbq)) {}
 
     status_t connect(const sp<IProducerListener>& listener, int api, bool producerControlledByApp,
                      QueueBufferOutput* output) override {
@@ -1082,6 +1088,26 @@
                                             producerControlledByApp, output);
     }
 
+    // We want to resize the frame history when changing the size of the buffer queue
+    status_t setMaxDequeuedBufferCount(int maxDequeuedBufferCount) override {
+        int maxBufferCount;
+        status_t status = BufferQueueProducer::setMaxDequeuedBufferCount(maxDequeuedBufferCount,
+                                                                         &maxBufferCount);
+        // if we can't determine the max buffer count, then just skip growing the history size
+        if (status == OK) {
+            size_t newFrameHistorySize = maxBufferCount + 2; // +2 because triple buffer rendering
+            // optimize away resizing the frame history unless it will grow
+            if (newFrameHistorySize > FrameEventHistory::INITIAL_MAX_FRAME_HISTORY) {
+                sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote();
+                if (bbq != nullptr) {
+                    ALOGV("increasing frame history size to %zu", newFrameHistorySize);
+                    bbq->resizeFrameEventHistory(newFrameHistorySize);
+                }
+            }
+        }
+        return status;
+    }
+
     int query(int what, int* value) override {
         if (what == NATIVE_WINDOW_QUEUES_TO_WINDOW_COMPOSER) {
             *value = 1;
@@ -1089,6 +1115,9 @@
         }
         return BufferQueueProducer::query(what, value);
     }
+
+private:
+    const wp<BLASTBufferQueue> mBLASTBufferQueue;
 };
 
 // Similar to BufferQueue::createBufferQueue but creates an adapter specific bufferqueue producer.
@@ -1103,7 +1132,7 @@
     sp<BufferQueueCore> core(new BufferQueueCore());
     LOG_ALWAYS_FATAL_IF(core == nullptr, "BLASTBufferQueue: failed to create BufferQueueCore");
 
-    sp<IGraphicBufferProducer> producer(new BBQBufferQueueProducer(core));
+    sp<IGraphicBufferProducer> producer(new BBQBufferQueueProducer(core, this));
     LOG_ALWAYS_FATAL_IF(producer == nullptr,
                         "BLASTBufferQueue: failed to create BBQBufferQueueProducer");
 
@@ -1116,6 +1145,16 @@
     *outConsumer = consumer;
 }
 
+void BLASTBufferQueue::resizeFrameEventHistory(size_t newSize) {
+    // This can be null during creation of the buffer queue, but resizing won't do anything at that
+    // point in time, so just ignore. This can go away once the class relationships and lifetimes of
+    // objects are cleaned up with a major refactor of BufferQueue as a whole.
+    if (mBufferItemConsumer != nullptr) {
+        std::unique_lock _lock{mMutex};
+        mBufferItemConsumer->resizeFrameEventHistory(newSize);
+    }
+}
+
 PixelFormat BLASTBufferQueue::convertBufferFormat(PixelFormat& format) {
     PixelFormat convertedFormat = format;
     switch (format) {
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index 5fe5e71..9eb1a9f 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -119,6 +119,12 @@
 
 status_t BufferQueueProducer::setMaxDequeuedBufferCount(
         int maxDequeuedBuffers) {
+    int maxBufferCount;
+    return setMaxDequeuedBufferCount(maxDequeuedBuffers, &maxBufferCount);
+}
+
+status_t BufferQueueProducer::setMaxDequeuedBufferCount(int maxDequeuedBuffers,
+                                                        int* maxBufferCount) {
     ATRACE_CALL();
     BQ_LOGV("setMaxDequeuedBufferCount: maxDequeuedBuffers = %d",
             maxDequeuedBuffers);
@@ -134,6 +140,8 @@
             return NO_INIT;
         }
 
+        *maxBufferCount = mCore->getMaxBufferCountLocked();
+
         if (maxDequeuedBuffers == mCore->mMaxDequeuedBufferCount) {
             return NO_ERROR;
         }
@@ -183,6 +191,7 @@
             return BAD_VALUE;
         }
         mCore->mMaxDequeuedBufferCount = maxDequeuedBuffers;
+        *maxBufferCount = mCore->getMaxBufferCountLocked();
         VALIDATE_CONSISTENCY();
         if (delta < 0) {
             listener = mCore->mConsumerListener;
diff --git a/libs/gui/FrameTimestamps.cpp b/libs/gui/FrameTimestamps.cpp
index e2ea3f9..f3eb4e8 100644
--- a/libs/gui/FrameTimestamps.cpp
+++ b/libs/gui/FrameTimestamps.cpp
@@ -168,10 +168,11 @@
 
 }  // namespace
 
-const size_t FrameEventHistory::MAX_FRAME_HISTORY =
+const size_t FrameEventHistory::INITIAL_MAX_FRAME_HISTORY =
         sysprop::LibGuiProperties::frame_event_history_size().value_or(8);
 
-FrameEventHistory::FrameEventHistory() : mFrames(std::vector<FrameEvents>(MAX_FRAME_HISTORY)) {}
+FrameEventHistory::FrameEventHistory()
+      : mFrames(std::vector<FrameEvents>(INITIAL_MAX_FRAME_HISTORY)) {}
 
 FrameEventHistory::~FrameEventHistory() = default;
 
@@ -227,7 +228,6 @@
     }
 }
 
-
 // ============================================================================
 // ProducerFrameEventHistory
 // ============================================================================
@@ -273,6 +273,13 @@
         const FrameEventHistoryDelta& delta) {
     mCompositorTiming = delta.mCompositorTiming;
 
+    // Deltas should have enough reserved capacity for the consumer-side, therefore if there's a
+    // different capacity, we re-sized on the consumer side and now need to resize on the producer
+    // side.
+    if (delta.mDeltas.capacity() > mFrames.capacity()) {
+        resize(delta.mDeltas.capacity());
+    }
+
     for (auto& d : delta.mDeltas) {
         // Avoid out-of-bounds access.
         if (CC_UNLIKELY(d.mIndex >= mFrames.size())) {
@@ -349,13 +356,48 @@
     return std::make_shared<FenceTime>(fence);
 }
 
+void ProducerFrameEventHistory::resize(size_t newSize) {
+    // we don't want to drop events by resizing too small, so don't resize in the negative direction
+    if (newSize <= mFrames.size()) {
+        return;
+    }
+
+    // This algorithm for resizing needs to be the same as ConsumerFrameEventHistory::resize,
+    // because the indexes need to match when communicating the FrameEventDeltas.
+
+    // We need to find the oldest frame, because that frame needs to move to index 0 in the new
+    // frame history.
+    size_t oldestFrameIndex = 0;
+    size_t oldestFrameNumber = INT32_MAX;
+    for (size_t i = 0; i < mFrames.size(); ++i) {
+        if (mFrames[i].frameNumber < oldestFrameNumber && mFrames[i].valid) {
+            oldestFrameNumber = mFrames[i].frameNumber;
+            oldestFrameIndex = i;
+        }
+    }
+
+    // move the existing frame information into a new vector, so that the oldest frames are at
+    // index 0, and the latest frames are at the end of the vector
+    std::vector<FrameEvents> newFrames(newSize);
+    size_t oldI = oldestFrameIndex;
+    size_t newI = 0;
+    do {
+        if (mFrames[oldI].valid) {
+            newFrames[newI++] = std::move(mFrames[oldI]);
+        }
+        oldI = (oldI + 1) % mFrames.size();
+    } while (oldI != oldestFrameIndex);
+
+    mFrames = std::move(newFrames);
+    mAcquireOffset = 0; // this is just a hint, so setting this to anything is fine
+}
 
 // ============================================================================
 // ConsumerFrameEventHistory
 // ============================================================================
 
 ConsumerFrameEventHistory::ConsumerFrameEventHistory()
-      : mFramesDirty(std::vector<FrameEventDirtyFields>(MAX_FRAME_HISTORY)) {}
+      : mFramesDirty(std::vector<FrameEventDirtyFields>(INITIAL_MAX_FRAME_HISTORY)) {}
 
 ConsumerFrameEventHistory::~ConsumerFrameEventHistory() = default;
 
@@ -489,6 +531,36 @@
     }
 }
 
+void ConsumerFrameEventHistory::resize(size_t newSize) {
+    // we don't want to drop events by resizing too small, so don't resize in the negative direction
+    if (newSize <= mFrames.size()) {
+        return;
+    }
+
+    // This algorithm for resizing needs to be the same as ProducerFrameEventHistory::resize,
+    // because the indexes need to match when communicating the FrameEventDeltas.
+
+    // move the existing frame information into a new vector, so that the oldest frames are at
+    // index 0, and the latest frames are towards the end of the vector
+    std::vector<FrameEvents> newFrames(newSize);
+    std::vector<FrameEventDirtyFields> newFramesDirty(newSize);
+    size_t oldestFrameIndex = mQueueOffset;
+    size_t oldI = oldestFrameIndex;
+    size_t newI = 0;
+    do {
+        if (mFrames[oldI].valid) {
+            newFrames[newI] = std::move(mFrames[oldI]);
+            newFramesDirty[newI] = mFramesDirty[oldI];
+            newI += 1;
+        }
+        oldI = (oldI + 1) % mFrames.size();
+    } while (oldI != oldestFrameIndex);
+
+    mFrames = std::move(newFrames);
+    mFramesDirty = std::move(newFramesDirty);
+    mQueueOffset = newI;
+    mCompositionOffset = 0; // this is just a hint, so setting this to anything is fine
+}
 
 // ============================================================================
 // FrameEventsDelta
@@ -558,8 +630,7 @@
         return NO_MEMORY;
     }
 
-    if (mIndex >= FrameEventHistory::MAX_FRAME_HISTORY ||
-            mIndex > std::numeric_limits<uint16_t>::max()) {
+    if (mIndex >= UINT8_MAX || mIndex < 0) {
         return BAD_VALUE;
     }
 
@@ -601,7 +672,7 @@
     uint16_t temp16 = 0;
     FlattenableUtils::read(buffer, size, temp16);
     mIndex = temp16;
-    if (mIndex >= FrameEventHistory::MAX_FRAME_HISTORY) {
+    if (mIndex >= UINT8_MAX) {
         return BAD_VALUE;
     }
     uint8_t temp8 = 0;
@@ -627,6 +698,25 @@
     return NO_ERROR;
 }
 
+uint64_t FrameEventsDelta::getFrameNumber() const {
+    return mFrameNumber;
+}
+
+bool FrameEventsDelta::getLatchTime(nsecs_t* latchTime) const {
+    if (mLatchTime == FrameEvents::TIMESTAMP_PENDING) {
+        return false;
+    }
+    *latchTime = mLatchTime;
+    return true;
+}
+
+bool FrameEventsDelta::getDisplayPresentFence(sp<Fence>* fence) const {
+    if (mDisplayPresentFence.fence == Fence::NO_FENCE) {
+        return false;
+    }
+    *fence = mDisplayPresentFence.fence;
+    return true;
+}
 
 // ============================================================================
 // FrameEventHistoryDelta
@@ -665,7 +755,7 @@
 
 status_t FrameEventHistoryDelta::flatten(
             void*& buffer, size_t& size, int*& fds, size_t& count) const {
-    if (mDeltas.size() > FrameEventHistory::MAX_FRAME_HISTORY) {
+    if (mDeltas.size() > UINT8_MAX) {
         return BAD_VALUE;
     }
     if (size < getFlattenedSize()) {
@@ -695,7 +785,7 @@
 
     uint32_t deltaCount = 0;
     FlattenableUtils::read(buffer, size, deltaCount);
-    if (deltaCount > FrameEventHistory::MAX_FRAME_HISTORY) {
+    if (deltaCount > UINT8_MAX) {
         return BAD_VALUE;
     }
     mDeltas.resize(deltaCount);
@@ -708,5 +798,12 @@
     return NO_ERROR;
 }
 
+std::vector<FrameEventsDelta>::const_iterator FrameEventHistoryDelta::begin() const {
+    return mDeltas.begin();
+}
+
+std::vector<FrameEventsDelta>::const_iterator FrameEventHistoryDelta::end() const {
+    return mDeltas.end();
+}
 
 } // namespace android
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 0f138ca..2f5830d 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -1363,7 +1363,8 @@
         (mask & layer_state_t::eLayerSecure) || (mask & layer_state_t::eLayerSkipScreenshot) ||
         (mask & layer_state_t::eEnableBackpressure) ||
         (mask & layer_state_t::eIgnoreDestinationFrame) ||
-        (mask & layer_state_t::eLayerIsDisplayDecoration)) {
+        (mask & layer_state_t::eLayerIsDisplayDecoration) ||
+        (mask & layer_state_t::eLayerIsRefreshRateIndicator)) {
         s->what |= layer_state_t::eFlagsChanged;
     }
     s->flags &= ~mask;
diff --git a/libs/gui/VsyncEventData.cpp b/libs/gui/VsyncEventData.cpp
index 23f0921..76c60c2 100644
--- a/libs/gui/VsyncEventData.cpp
+++ b/libs/gui/VsyncEventData.cpp
@@ -23,6 +23,9 @@
 
 namespace android::gui {
 
+static_assert(VsyncEventData::kFrameTimelinesLength == 7,
+              "Must update value in DisplayEventReceiver.java#FRAME_TIMELINES_LENGTH (and here)");
+
 int64_t VsyncEventData::preferredVsyncId() const {
     return frameTimelines[preferredFrameTimelineIndex].vsyncId;
 }
diff --git a/libs/gui/WindowInfosListenerReporter.cpp b/libs/gui/WindowInfosListenerReporter.cpp
index 01e865d..2b34a0f 100644
--- a/libs/gui/WindowInfosListenerReporter.cpp
+++ b/libs/gui/WindowInfosListenerReporter.cpp
@@ -62,6 +62,10 @@
     status_t status = OK;
     {
         std::scoped_lock lock(mListenersMutex);
+        if (mWindowInfosListeners.find(windowInfosListener) == mWindowInfosListeners.end()) {
+            return status;
+        }
+
         if (mWindowInfosListeners.size() == 1) {
             binder::Status s = surfaceComposer->removeWindowInfosListener(this);
             status = statusTFromBinderStatus(s);
diff --git a/libs/gui/aidl/android/gui/IHdrLayerInfoListener.aidl b/libs/gui/aidl/android/gui/IHdrLayerInfoListener.aidl
index fc809c4..e8c36ee 100644
--- a/libs/gui/aidl/android/gui/IHdrLayerInfoListener.aidl
+++ b/libs/gui/aidl/android/gui/IHdrLayerInfoListener.aidl
@@ -19,7 +19,9 @@
 /** @hide */
 oneway interface IHdrLayerInfoListener {
     // Callback with the total number of HDR layers, the dimensions of the largest layer,
-    // and a placeholder flags
+    // a placeholder flags, and the max desired HDR/SDR ratio. The max desired HDR/SDR
+    // ratio may be positive infinity to indicate an unbounded ratio.
     // TODO (b/182312559): Define the flags (likely need an indicator that a UDFPS layer is present)
-    void onHdrLayerInfoChanged(int numberOfHdrLayers, int maxW, int maxH, int flags);
+    void onHdrLayerInfoChanged(int numberOfHdrLayers, int maxW, int maxH,
+            int flags, float maxDesiredHdrSdrRatio);
 }
\ No newline at end of file
diff --git a/libs/gui/bufferqueue/1.0/Conversion.cpp b/libs/gui/bufferqueue/1.0/Conversion.cpp
index 55462c3..9667954 100644
--- a/libs/gui/bufferqueue/1.0/Conversion.cpp
+++ b/libs/gui/bufferqueue/1.0/Conversion.cpp
@@ -725,12 +725,7 @@
     // These were written as uint8_t for alignment.
     uint8_t temp = 0;
     FlattenableUtils::read(buffer, size, temp);
-    size_t index = static_cast<size_t>(temp);
-    if (index >= ::android::FrameEventHistory::MAX_FRAME_HISTORY) {
-        return BAD_VALUE;
-    }
-    t->index = static_cast<uint32_t>(index);
-
+    t->index = static_cast<uint32_t>(temp);
     FlattenableUtils::read(buffer, size, temp);
     t->addPostCompositeCalled = static_cast<bool>(temp);
     FlattenableUtils::read(buffer, size, temp);
@@ -786,8 +781,7 @@
 status_t flatten(HGraphicBufferProducer::FrameEventsDelta const& t,
         void*& buffer, size_t& size, int*& fds, size_t numFds) {
     // Check that t.index is within a valid range.
-    if (t.index >= static_cast<uint32_t>(FrameEventHistory::MAX_FRAME_HISTORY)
-            || t.index > std::numeric_limits<uint8_t>::max()) {
+    if (t.index > UINT8_MAX || t.index < 0) {
         return BAD_VALUE;
     }
 
@@ -887,8 +881,7 @@
 
     uint32_t deltaCount = 0;
     FlattenableUtils::read(buffer, size, deltaCount);
-    if (static_cast<size_t>(deltaCount) >
-            ::android::FrameEventHistory::MAX_FRAME_HISTORY) {
+    if (deltaCount > UINT8_MAX) {
         return BAD_VALUE;
     }
     t->deltas.resize(deltaCount);
@@ -919,7 +912,7 @@
 status_t flatten(
         HGraphicBufferProducer::FrameEventHistoryDelta const& t,
         void*& buffer, size_t& size, int*& fds, size_t& numFds) {
-    if (t.deltas.size() > ::android::FrameEventHistory::MAX_FRAME_HISTORY) {
+    if (t.deltas.size() > UINT8_MAX) {
         return BAD_VALUE;
     }
     if (size < getFlattenedSize(t)) {
diff --git a/libs/gui/bufferqueue/1.0/H2BGraphicBufferProducer.cpp b/libs/gui/bufferqueue/1.0/H2BGraphicBufferProducer.cpp
index cee1b81..f684874 100644
--- a/libs/gui/bufferqueue/1.0/H2BGraphicBufferProducer.cpp
+++ b/libs/gui/bufferqueue/1.0/H2BGraphicBufferProducer.cpp
@@ -725,8 +725,7 @@
         std::vector<native_handle_t*>* nh,
         void*& buffer, size_t& size, int*& fds, size_t numFds) {
     // Check that t.index is within a valid range.
-    if (t.index >= static_cast<uint32_t>(FrameEventHistory::MAX_FRAME_HISTORY)
-            || t.index > std::numeric_limits<uint8_t>::max()) {
+    if (t.index > UINT8_MAX || t.index < 0) {
         return BAD_VALUE;
     }
 
@@ -829,7 +828,7 @@
         HGraphicBufferProducer::FrameEventHistoryDelta const& t,
         std::vector<std::vector<native_handle_t*> >* nh,
         void*& buffer, size_t& size, int*& fds, size_t& numFds) {
-    if (t.deltas.size() > ::android::FrameEventHistory::MAX_FRAME_HISTORY) {
+    if (t.deltas.size() > UINT8_MAX) {
         return BAD_VALUE;
     }
     if (size < getFlattenedSize(t)) {
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index b9e0647..69e9f8a 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -54,6 +54,8 @@
                                nsecs_t dequeueReadyTime) EXCLUDES(mMutex);
     void getConnectionEvents(uint64_t frameNumber, bool* needsDisconnect) EXCLUDES(mMutex);
 
+    void resizeFrameEventHistory(size_t newSize);
+
 protected:
     void onSidebandStreamChanged() override EXCLUDES(mMutex);
 
@@ -123,6 +125,7 @@
 
 private:
     friend class BLASTBufferQueueHelper;
+    friend class BBQBufferQueueProducer;
 
     // can't be copied
     BLASTBufferQueue& operator = (const BLASTBufferQueue& rhs);
@@ -130,6 +133,8 @@
     void createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
                            sp<IGraphicBufferConsumer>* outConsumer);
 
+    void resizeFrameEventHistory(size_t newSize);
+
     status_t acquireNextBufferLocked(
             const std::optional<SurfaceComposerClient::Transaction*> transaction) REQUIRES(mMutex);
     Rect computeCrop(const BufferItem& item) REQUIRES(mMutex);
diff --git a/libs/gui/include/gui/BufferQueueProducer.h b/libs/gui/include/gui/BufferQueueProducer.h
index 0ad3075..1d13dab 100644
--- a/libs/gui/include/gui/BufferQueueProducer.h
+++ b/libs/gui/include/gui/BufferQueueProducer.h
@@ -202,6 +202,11 @@
     // See IGraphicBufferProducer::setAutoPrerotation
     virtual status_t setAutoPrerotation(bool autoPrerotation);
 
+protected:
+    // see IGraphicsBufferProducer::setMaxDequeuedBufferCount, but with the ability to retrieve the
+    // total maximum buffer count for the buffer queue (dequeued AND acquired)
+    status_t setMaxDequeuedBufferCount(int maxDequeuedBuffers, int* maxBufferCount);
+
 private:
     // This is required by the IBinder::DeathRecipient interface
     virtual void binderDied(const wp<IBinder>& who);
diff --git a/libs/gui/include/gui/FrameTimestamps.h b/libs/gui/include/gui/FrameTimestamps.h
index c08a9b1..3d1be4d 100644
--- a/libs/gui/include/gui/FrameTimestamps.h
+++ b/libs/gui/include/gui/FrameTimestamps.h
@@ -97,9 +97,11 @@
     void checkFencesForCompletion();
     void dump(std::string& outString) const;
 
-    static const size_t MAX_FRAME_HISTORY;
+    static const size_t INITIAL_MAX_FRAME_HISTORY;
 
 protected:
+    void resize(size_t newSize);
+
     std::vector<FrameEvents> mFrames;
 
     CompositorTiming mCompositorTiming;
@@ -138,6 +140,8 @@
     virtual std::shared_ptr<FenceTime> createFenceTime(
             const sp<Fence>& fence) const;
 
+    void resize(size_t newSize);
+
     size_t mAcquireOffset{0};
 
     // The consumer updates it's timelines in Layer and SurfaceFlinger since
@@ -208,6 +212,8 @@
 
     void getAndResetDelta(FrameEventHistoryDelta* delta);
 
+    void resize(size_t newSize);
+
 private:
     void getFrameDelta(FrameEventHistoryDelta* delta,
                        const std::vector<FrameEvents>::iterator& frame);
@@ -250,6 +256,10 @@
     status_t unflatten(void const*& buffer, size_t& size, int const*& fds,
             size_t& count);
 
+    uint64_t getFrameNumber() const;
+    bool getLatchTime(nsecs_t* latchTime) const;
+    bool getDisplayPresentFence(sp<Fence>* fence) const;
+
 private:
     static constexpr size_t minFlattenedSize();
 
@@ -310,6 +320,9 @@
     status_t unflatten(void const*& buffer, size_t& size, int const*& fds,
             size_t& count);
 
+    std::vector<FrameEventsDelta>::const_iterator begin() const;
+    std::vector<FrameEventsDelta>::const_iterator end() const;
+
 private:
     static constexpr size_t minFlattenedSize();
 
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 29fb989..6e3be5c 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -160,6 +160,7 @@
         // This is needed to maintain compatibility for SurfaceView scaling behavior.
         // See SurfaceView scaling behavior for more details.
         eIgnoreDestinationFrame = 0x400,
+        eLayerIsRefreshRateIndicator = 0x800, // REFRESH_RATE_INDICATOR
     };
 
     enum {
diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp
index 183acc1..cd35d2f 100644
--- a/libs/gui/tests/Android.bp
+++ b/libs/gui/tests/Android.bp
@@ -91,6 +91,9 @@
     compile_multilib: "both",
 
     header_libs: ["libsurfaceflinger_headers"],
+    data: [
+        ":libgui_test",
+    ],
 }
 
 cc_test {
diff --git a/libs/gui/view/Surface.cpp b/libs/gui/view/Surface.cpp
index 1bfe462..198908d 100644
--- a/libs/gui/view/Surface.cpp
+++ b/libs/gui/view/Surface.cpp
@@ -16,17 +16,59 @@
 
 #define LOG_TAG "Surface"
 
-#include <gui/view/Surface.h>
-
+#include <android/binder_libbinder.h>
+#include <android/binder_parcel.h>
+#include <android/native_window.h>
 #include <binder/Parcel.h>
-
-#include <utils/Log.h>
-
 #include <gui/IGraphicBufferProducer.h>
+#include <gui/Surface.h>
+#include <gui/view/Surface.h>
+#include <system/window.h>
+#include <utils/Log.h>
 
 namespace android {
 namespace view {
 
+// Since this is a parcelable utility and we want to keep the wire format stable, only build this
+// when building the system libgui to detect any issues loading the wrong libgui from
+// libnativewindow
+
+#if (!defined(__ANDROID_APEX__) && !defined(__ANDROID_VNDK__))
+
+extern "C" status_t android_view_Surface_writeToParcel(ANativeWindow* _Nonnull window,
+                                                       Parcel* _Nonnull parcel) {
+    int value;
+    int err = (*window->query)(window, NATIVE_WINDOW_CONCRETE_TYPE, &value);
+    if (err != OK || value != NATIVE_WINDOW_SURFACE) {
+        ALOGE("Error: ANativeWindow is not backed by Surface");
+        return STATUS_BAD_VALUE;
+    }
+    // Use a android::view::Surface to parcelize the window
+    android::view::Surface shimSurface;
+    shimSurface.graphicBufferProducer = android::Surface::getIGraphicBufferProducer(window);
+    shimSurface.surfaceControlHandle = android::Surface::getSurfaceControlHandle(window);
+    return shimSurface.writeToParcel(parcel);
+}
+
+extern "C" status_t android_view_Surface_readFromParcel(
+        const Parcel* _Nonnull parcel, ANativeWindow* _Nullable* _Nonnull outWindow) {
+    // Use a android::view::Surface to unparcel the window
+    android::view::Surface shimSurface;
+    status_t ret = shimSurface.readFromParcel(parcel);
+    if (ret != OK) {
+        ALOGE("%s: Error: Failed to create android::view::Surface from AParcel", __FUNCTION__);
+        return STATUS_BAD_VALUE;
+    }
+    auto surface = sp<android::Surface>::make(shimSurface.graphicBufferProducer, false,
+                                              shimSurface.surfaceControlHandle);
+    ANativeWindow* anw = surface.get();
+    ANativeWindow_acquire(anw);
+    *outWindow = anw;
+    return STATUS_OK;
+}
+
+#endif
+
 status_t Surface::writeToParcel(Parcel* parcel) const {
     return writeToParcel(parcel, false);
 }
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index f38dd98..869458c 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -167,6 +167,7 @@
 
 cc_defaults {
     name: "libinput_fuzz_defaults",
+    cpp_std: "c++20",
     host_supported: true,
     shared_libs: [
         "libutils",
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index 133b260..53b22cb 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -151,10 +151,23 @@
 
 // --- InputEvent ---
 
+// Due to precision limitations when working with floating points, transforming - namely
+// scaling - floating points can lead to minute errors. We round transformed values to approximately
+// three decimal places so that values like 0.99997 show up as 1.0.
+inline float roundTransformedCoords(float val) {
+    // Use a power to two to approximate three decimal places to potentially reduce some cycles.
+    // This should be at least as precise as MotionEvent::ROUNDING_PRECISION.
+    return std::round(val * 1024.f) / 1024.f;
+}
+
+inline vec2 roundTransformedCoords(vec2 p) {
+    return {roundTransformedCoords(p.x), roundTransformedCoords(p.y)};
+}
+
 vec2 transformWithoutTranslation(const ui::Transform& transform, const vec2& xy) {
     const vec2 transformedXy = transform.transform(xy);
     const vec2 transformedOrigin = transform.transform(0, 0);
-    return transformedXy - transformedOrigin;
+    return roundTransformedCoords(transformedXy - transformedOrigin);
 }
 
 float transformAngle(const ui::Transform& transform, float angleRadians) {
@@ -606,12 +619,12 @@
 
 float MotionEvent::getXCursorPosition() const {
     vec2 vals = mTransform.transform(getRawXCursorPosition(), getRawYCursorPosition());
-    return vals.x;
+    return roundTransformedCoords(vals.x);
 }
 
 float MotionEvent::getYCursorPosition() const {
     vec2 vals = mTransform.transform(getRawXCursorPosition(), getRawYCursorPosition());
-    return vals.y;
+    return roundTransformedCoords(vals.y);
 }
 
 void MotionEvent::setCursorPosition(float x, float y) {
@@ -933,7 +946,7 @@
 static inline vec2 calculateTransformedXYUnchecked(uint32_t source, const ui::Transform& transform,
                                                    const vec2& xy) {
     return shouldDisregardOffset(source) ? transformWithoutTranslation(transform, xy)
-                                         : transform.transform(xy);
+                                         : roundTransformedCoords(transform.transform(xy));
 }
 
 vec2 MotionEvent::calculateTransformedXY(uint32_t source, const ui::Transform& transform,
diff --git a/libs/input/Keyboard.cpp b/libs/input/Keyboard.cpp
index 3f8467d..0b5c7ff 100644
--- a/libs/input/Keyboard.cpp
+++ b/libs/input/Keyboard.cpp
@@ -16,9 +16,10 @@
 
 #define LOG_TAG "Keyboard"
 
+#include <limits.h>
 #include <stdlib.h>
 #include <unistd.h>
-#include <limits.h>
+#include <optional>
 
 #include <input/InputDevice.h>
 #include <input/InputEventLabels.h>
@@ -49,23 +50,25 @@
         const PropertyMap* deviceConfiguration) {
     // Use the configured key layout if available.
     if (deviceConfiguration) {
-        std::string keyLayoutName;
-        if (deviceConfiguration->tryGetProperty("keyboard.layout", keyLayoutName)) {
-            status_t status = loadKeyLayout(deviceIdentifier, keyLayoutName.c_str());
+        std::optional<std::string> keyLayoutName =
+                deviceConfiguration->getString("keyboard.layout");
+        if (keyLayoutName.has_value()) {
+            status_t status = loadKeyLayout(deviceIdentifier, *keyLayoutName);
             if (status == NAME_NOT_FOUND) {
                 ALOGE("Configuration for keyboard device '%s' requested keyboard layout '%s' but "
                       "it was not found.",
-                      deviceIdentifier.name.c_str(), keyLayoutName.c_str());
+                      deviceIdentifier.name.c_str(), keyLayoutName->c_str());
             }
         }
 
-        std::string keyCharacterMapName;
-        if (deviceConfiguration->tryGetProperty("keyboard.characterMap", keyCharacterMapName)) {
-            status_t status = loadKeyCharacterMap(deviceIdentifier, keyCharacterMapName.c_str());
+        std::optional<std::string> keyCharacterMapName =
+                deviceConfiguration->getString("keyboard.characterMap");
+        if (keyCharacterMapName.has_value()) {
+            status_t status = loadKeyCharacterMap(deviceIdentifier, *keyCharacterMapName);
             if (status == NAME_NOT_FOUND) {
                 ALOGE("Configuration for keyboard device '%s' requested keyboard character "
                       "map '%s' but it was not found.",
-                      deviceIdentifier.name.c_str(), keyCharacterMapName.c_str());
+                      deviceIdentifier.name.c_str(), keyCharacterMapName->c_str());
             }
         }
 
@@ -162,9 +165,7 @@
     if (config == nullptr) {
         return false;
     }
-    bool isSpecialFunction = false;
-    config->tryGetProperty("keyboard.specialFunction", isSpecialFunction);
-    return isSpecialFunction;
+    return config->getBool("keyboard.specialFunction").value_or(false);
 }
 
 bool isEligibleBuiltInKeyboard(const InputDeviceIdentifier& deviceIdentifier,
@@ -177,8 +178,7 @@
     }
 
     if (deviceConfiguration) {
-        bool builtIn = false;
-        if (deviceConfiguration->tryGetProperty("keyboard.builtIn", builtIn) && builtIn) {
+        if (deviceConfiguration->getBool("keyboard.builtIn").value_or(false)) {
             return true;
         }
     }
diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp
index 7d11ef2..b4151c6 100644
--- a/libs/input/MotionPredictor.cpp
+++ b/libs/input/MotionPredictor.cpp
@@ -35,7 +35,6 @@
 namespace android {
 namespace {
 
-const char DEFAULT_MODEL_PATH[] = "/system/etc/motion_predictor_model.fb";
 const int64_t PREDICTION_INTERVAL_NANOS =
         12500000 / 3; // TODO(b/266747937): Get this from the model.
 
@@ -62,48 +61,58 @@
 
 // --- MotionPredictor ---
 
-MotionPredictor::MotionPredictor(nsecs_t predictionTimestampOffsetNanos, const char* modelPath,
+MotionPredictor::MotionPredictor(nsecs_t predictionTimestampOffsetNanos,
                                  std::function<bool()> checkMotionPredictionEnabled)
       : mPredictionTimestampOffsetNanos(predictionTimestampOffsetNanos),
-        mModelPath(modelPath == nullptr ? DEFAULT_MODEL_PATH : modelPath),
         mCheckMotionPredictionEnabled(std::move(checkMotionPredictionEnabled)) {}
 
-void MotionPredictor::record(const MotionEvent& event) {
+android::base::Result<void> MotionPredictor::record(const MotionEvent& event) {
+    if (mLastEvent && mLastEvent->getDeviceId() != event.getDeviceId()) {
+        // We still have an active gesture for another device. The provided MotionEvent is not
+        // consistent the previous gesture.
+        LOG(ERROR) << "Inconsistent event stream: last event is " << *mLastEvent << ", but "
+                   << __func__ << " is called with " << event;
+        return android::base::Error()
+                << "Inconsistent event stream: still have an active gesture from device "
+                << mLastEvent->getDeviceId() << ", but received " << event;
+    }
     if (!isPredictionAvailable(event.getDeviceId(), event.getSource())) {
         ALOGE("Prediction not supported for device %d's %s source", event.getDeviceId(),
               inputEventSourceToString(event.getSource()).c_str());
-        return;
+        return {};
     }
 
     // Initialise the model now that it's likely to be used.
     if (!mModel) {
-        mModel = TfLiteMotionPredictorModel::create(mModelPath.c_str());
+        mModel = TfLiteMotionPredictorModel::create();
     }
 
-    TfLiteMotionPredictorBuffers& buffers =
-            mDeviceBuffers.try_emplace(event.getDeviceId(), mModel->inputLength()).first->second;
+    if (mBuffers == nullptr) {
+        mBuffers = std::make_unique<TfLiteMotionPredictorBuffers>(mModel->inputLength());
+    }
 
     const int32_t action = event.getActionMasked();
-    if (action == AMOTION_EVENT_ACTION_UP) {
+    if (action == AMOTION_EVENT_ACTION_UP || action == AMOTION_EVENT_ACTION_CANCEL) {
         ALOGD_IF(isDebug(), "End of event stream");
-        buffers.reset();
-        return;
+        mBuffers->reset();
+        mLastEvent.reset();
+        return {};
     } else if (action != AMOTION_EVENT_ACTION_DOWN && action != AMOTION_EVENT_ACTION_MOVE) {
         ALOGD_IF(isDebug(), "Skipping unsupported %s action",
                  MotionEvent::actionToString(action).c_str());
-        return;
+        return {};
     }
 
     if (event.getPointerCount() != 1) {
         ALOGD_IF(isDebug(), "Prediction not supported for multiple pointers");
-        return;
+        return {};
     }
 
     const int32_t toolType = event.getPointerProperties(0)->toolType;
     if (toolType != AMOTION_EVENT_TOOL_TYPE_STYLUS) {
         ALOGD_IF(isDebug(), "Prediction not supported for non-stylus tool: %s",
                  motionToolTypeToString(toolType));
-        return;
+        return {};
     }
 
     for (size_t i = 0; i <= event.getHistorySize(); ++i) {
@@ -111,100 +120,98 @@
             continue;
         }
         const PointerCoords* coords = event.getHistoricalRawPointerCoords(0, i);
-        buffers.pushSample(event.getHistoricalEventTime(i),
-                           {
-                                   .position.x = coords->getAxisValue(AMOTION_EVENT_AXIS_X),
-                                   .position.y = coords->getAxisValue(AMOTION_EVENT_AXIS_Y),
-                                   .pressure = event.getHistoricalPressure(0, i),
-                                   .tilt = event.getHistoricalAxisValue(AMOTION_EVENT_AXIS_TILT, 0,
-                                                                        i),
-                                   .orientation = event.getHistoricalOrientation(0, i),
-                           });
+        mBuffers->pushSample(event.getHistoricalEventTime(i),
+                             {
+                                     .position.x = coords->getAxisValue(AMOTION_EVENT_AXIS_X),
+                                     .position.y = coords->getAxisValue(AMOTION_EVENT_AXIS_Y),
+                                     .pressure = event.getHistoricalPressure(0, i),
+                                     .tilt = event.getHistoricalAxisValue(AMOTION_EVENT_AXIS_TILT,
+                                                                          0, i),
+                                     .orientation = event.getHistoricalOrientation(0, i),
+                             });
     }
 
-    mLastEvents.try_emplace(event.getDeviceId())
-            .first->second.copyFrom(&event, /*keepHistory=*/false);
+    if (!mLastEvent) {
+        mLastEvent = MotionEvent();
+    }
+    mLastEvent->copyFrom(&event, /*keepHistory=*/false);
+    return {};
 }
 
-std::vector<std::unique_ptr<MotionEvent>> MotionPredictor::predict(nsecs_t timestamp) {
-    std::vector<std::unique_ptr<MotionEvent>> predictions;
-
-    for (const auto& [deviceId, buffer] : mDeviceBuffers) {
-        if (!buffer.isReady()) {
-            continue;
-        }
-
-        LOG_ALWAYS_FATAL_IF(!mModel);
-        buffer.copyTo(*mModel);
-        LOG_ALWAYS_FATAL_IF(!mModel->invoke());
-
-        // Read out the predictions.
-        const std::span<const float> predictedR = mModel->outputR();
-        const std::span<const float> predictedPhi = mModel->outputPhi();
-        const std::span<const float> predictedPressure = mModel->outputPressure();
-
-        TfLiteMotionPredictorSample::Point axisFrom = buffer.axisFrom().position;
-        TfLiteMotionPredictorSample::Point axisTo = buffer.axisTo().position;
-
-        if (isDebug()) {
-            ALOGD("deviceId: %d", deviceId);
-            ALOGD("axisFrom: %f, %f", axisFrom.x, axisFrom.y);
-            ALOGD("axisTo: %f, %f", axisTo.x, axisTo.y);
-            ALOGD("mInputR: %s", base::Join(mModel->inputR(), ", ").c_str());
-            ALOGD("mInputPhi: %s", base::Join(mModel->inputPhi(), ", ").c_str());
-            ALOGD("mInputPressure: %s", base::Join(mModel->inputPressure(), ", ").c_str());
-            ALOGD("mInputTilt: %s", base::Join(mModel->inputTilt(), ", ").c_str());
-            ALOGD("mInputOrientation: %s", base::Join(mModel->inputOrientation(), ", ").c_str());
-            ALOGD("predictedR: %s", base::Join(predictedR, ", ").c_str());
-            ALOGD("predictedPhi: %s", base::Join(predictedPhi, ", ").c_str());
-            ALOGD("predictedPressure: %s", base::Join(predictedPressure, ", ").c_str());
-        }
-
-        const MotionEvent& event = mLastEvents[deviceId];
-        bool hasPredictions = false;
-        std::unique_ptr<MotionEvent> prediction = std::make_unique<MotionEvent>();
-        int64_t predictionTime = buffer.lastTimestamp();
-        const int64_t futureTime = timestamp + mPredictionTimestampOffsetNanos;
-
-        for (int i = 0; i < predictedR.size() && predictionTime <= futureTime; ++i) {
-            const TfLiteMotionPredictorSample::Point point =
-                    convertPrediction(axisFrom, axisTo, predictedR[i], predictedPhi[i]);
-            // TODO(b/266747654): Stop predictions if confidence is < some threshold.
-
-            ALOGD_IF(isDebug(), "prediction %d: %f, %f", i, point.x, point.y);
-            PointerCoords coords;
-            coords.clear();
-            coords.setAxisValue(AMOTION_EVENT_AXIS_X, point.x);
-            coords.setAxisValue(AMOTION_EVENT_AXIS_Y, point.y);
-            // TODO(b/266747654): Stop predictions if predicted pressure is < some threshold.
-            coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, predictedPressure[i]);
-
-            predictionTime += PREDICTION_INTERVAL_NANOS;
-            if (i == 0) {
-                hasPredictions = true;
-                prediction->initialize(InputEvent::nextId(), event.getDeviceId(), event.getSource(),
-                                       event.getDisplayId(), INVALID_HMAC,
-                                       AMOTION_EVENT_ACTION_MOVE, event.getActionButton(),
-                                       event.getFlags(), event.getEdgeFlags(), event.getMetaState(),
-                                       event.getButtonState(), event.getClassification(),
-                                       event.getTransform(), event.getXPrecision(),
-                                       event.getYPrecision(), event.getRawXCursorPosition(),
-                                       event.getRawYCursorPosition(), event.getRawTransform(),
-                                       event.getDownTime(), predictionTime, event.getPointerCount(),
-                                       event.getPointerProperties(), &coords);
-            } else {
-                prediction->addSample(predictionTime, &coords);
-            }
-
-            axisFrom = axisTo;
-            axisTo = point;
-        }
-        // TODO(b/266747511): Interpolate to futureTime?
-        if (hasPredictions) {
-            predictions.push_back(std::move(prediction));
-        }
+std::unique_ptr<MotionEvent> MotionPredictor::predict(nsecs_t timestamp) {
+    if (mBuffers == nullptr || !mBuffers->isReady()) {
+        return nullptr;
     }
-    return predictions;
+
+    LOG_ALWAYS_FATAL_IF(!mModel);
+    mBuffers->copyTo(*mModel);
+    LOG_ALWAYS_FATAL_IF(!mModel->invoke());
+
+    // Read out the predictions.
+    const std::span<const float> predictedR = mModel->outputR();
+    const std::span<const float> predictedPhi = mModel->outputPhi();
+    const std::span<const float> predictedPressure = mModel->outputPressure();
+
+    TfLiteMotionPredictorSample::Point axisFrom = mBuffers->axisFrom().position;
+    TfLiteMotionPredictorSample::Point axisTo = mBuffers->axisTo().position;
+
+    if (isDebug()) {
+        ALOGD("axisFrom: %f, %f", axisFrom.x, axisFrom.y);
+        ALOGD("axisTo: %f, %f", axisTo.x, axisTo.y);
+        ALOGD("mInputR: %s", base::Join(mModel->inputR(), ", ").c_str());
+        ALOGD("mInputPhi: %s", base::Join(mModel->inputPhi(), ", ").c_str());
+        ALOGD("mInputPressure: %s", base::Join(mModel->inputPressure(), ", ").c_str());
+        ALOGD("mInputTilt: %s", base::Join(mModel->inputTilt(), ", ").c_str());
+        ALOGD("mInputOrientation: %s", base::Join(mModel->inputOrientation(), ", ").c_str());
+        ALOGD("predictedR: %s", base::Join(predictedR, ", ").c_str());
+        ALOGD("predictedPhi: %s", base::Join(predictedPhi, ", ").c_str());
+        ALOGD("predictedPressure: %s", base::Join(predictedPressure, ", ").c_str());
+    }
+
+    LOG_ALWAYS_FATAL_IF(!mLastEvent);
+    const MotionEvent& event = *mLastEvent;
+    bool hasPredictions = false;
+    std::unique_ptr<MotionEvent> prediction = std::make_unique<MotionEvent>();
+    int64_t predictionTime = mBuffers->lastTimestamp();
+    const int64_t futureTime = timestamp + mPredictionTimestampOffsetNanos;
+
+    for (int i = 0; i < predictedR.size() && predictionTime <= futureTime; ++i) {
+        const TfLiteMotionPredictorSample::Point point =
+                convertPrediction(axisFrom, axisTo, predictedR[i], predictedPhi[i]);
+        // TODO(b/266747654): Stop predictions if confidence is < some threshold.
+
+        ALOGD_IF(isDebug(), "prediction %d: %f, %f", i, point.x, point.y);
+        PointerCoords coords;
+        coords.clear();
+        coords.setAxisValue(AMOTION_EVENT_AXIS_X, point.x);
+        coords.setAxisValue(AMOTION_EVENT_AXIS_Y, point.y);
+        // TODO(b/266747654): Stop predictions if predicted pressure is < some threshold.
+        coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, predictedPressure[i]);
+
+        predictionTime += PREDICTION_INTERVAL_NANOS;
+        if (i == 0) {
+            hasPredictions = true;
+            prediction->initialize(InputEvent::nextId(), event.getDeviceId(), event.getSource(),
+                                   event.getDisplayId(), INVALID_HMAC, AMOTION_EVENT_ACTION_MOVE,
+                                   event.getActionButton(), event.getFlags(), event.getEdgeFlags(),
+                                   event.getMetaState(), event.getButtonState(),
+                                   event.getClassification(), event.getTransform(),
+                                   event.getXPrecision(), event.getYPrecision(),
+                                   event.getRawXCursorPosition(), event.getRawYCursorPosition(),
+                                   event.getRawTransform(), event.getDownTime(), predictionTime,
+                                   event.getPointerCount(), event.getPointerProperties(), &coords);
+        } else {
+            prediction->addSample(predictionTime, &coords);
+        }
+
+        axisFrom = axisTo;
+        axisTo = point;
+    }
+    // TODO(b/266747511): Interpolate to futureTime?
+    if (!hasPredictions) {
+        return nullptr;
+    }
+    return prediction;
 }
 
 bool MotionPredictor::isPredictionAvailable(int32_t /*deviceId*/, int32_t source) {
diff --git a/libs/input/PropertyMap.cpp b/libs/input/PropertyMap.cpp
index ed9ac9f..548f894 100644
--- a/libs/input/PropertyMap.cpp
+++ b/libs/input/PropertyMap.cpp
@@ -16,6 +16,8 @@
 
 #define LOG_TAG "PropertyMap"
 
+#include <cstdlib>
+
 #include <input/PropertyMap.h>
 #include <log/log.h>
 
@@ -44,62 +46,76 @@
     mProperties.emplace(key, value);
 }
 
+std::unordered_set<std::string> PropertyMap::getKeysWithPrefix(const std::string& prefix) const {
+    std::unordered_set<std::string> keys;
+    for (const auto& [key, _] : mProperties) {
+        if (key.starts_with(prefix)) {
+            keys.insert(key);
+        }
+    }
+    return keys;
+}
+
 bool PropertyMap::hasProperty(const std::string& key) const {
     return mProperties.find(key) != mProperties.end();
 }
 
-bool PropertyMap::tryGetProperty(const std::string& key, std::string& outValue) const {
+std::optional<std::string> PropertyMap::getString(const std::string& key) const {
     auto it = mProperties.find(key);
-    if (it == mProperties.end()) {
-        return false;
-    }
-
-    outValue = it->second;
-    return true;
+    return it != mProperties.end() ? std::make_optional(it->second) : std::nullopt;
 }
 
-bool PropertyMap::tryGetProperty(const std::string& key, bool& outValue) const {
-    int32_t intValue;
-    if (!tryGetProperty(key, intValue)) {
-        return false;
-    }
-
-    outValue = intValue;
-    return true;
+std::optional<bool> PropertyMap::getBool(const std::string& key) const {
+    std::optional<int32_t> intValue = getInt(key);
+    return intValue.has_value() ? std::make_optional(*intValue != 0) : std::nullopt;
 }
 
-bool PropertyMap::tryGetProperty(const std::string& key, int32_t& outValue) const {
-    std::string stringValue;
-    if (!tryGetProperty(key, stringValue) || stringValue.length() == 0) {
-        return false;
+std::optional<int32_t> PropertyMap::getInt(const std::string& key) const {
+    std::optional<std::string> stringValue = getString(key);
+    if (!stringValue.has_value() || stringValue->length() == 0) {
+        return std::nullopt;
     }
 
     char* end;
-    int32_t value = static_cast<int32_t>(strtol(stringValue.c_str(), &end, 10));
+    int32_t value = static_cast<int32_t>(strtol(stringValue->c_str(), &end, 10));
     if (*end != '\0') {
         ALOGW("Property key '%s' has invalid value '%s'.  Expected an integer.", key.c_str(),
-              stringValue.c_str());
-        return false;
+              stringValue->c_str());
+        return std::nullopt;
     }
-    outValue = value;
-    return true;
+    return value;
 }
 
-bool PropertyMap::tryGetProperty(const std::string& key, float& outValue) const {
-    std::string stringValue;
-    if (!tryGetProperty(key, stringValue) || stringValue.length() == 0) {
-        return false;
+std::optional<float> PropertyMap::getFloat(const std::string& key) const {
+    std::optional<std::string> stringValue = getString(key);
+    if (!stringValue.has_value() || stringValue->length() == 0) {
+        return std::nullopt;
     }
 
     char* end;
-    float value = strtof(stringValue.c_str(), &end);
+    float value = strtof(stringValue->c_str(), &end);
     if (*end != '\0') {
         ALOGW("Property key '%s' has invalid value '%s'.  Expected a float.", key.c_str(),
-              stringValue.c_str());
-        return false;
+              stringValue->c_str());
+        return std::nullopt;
     }
-    outValue = value;
-    return true;
+    return value;
+}
+
+std::optional<double> PropertyMap::getDouble(const std::string& key) const {
+    std::optional<std::string> stringValue = getString(key);
+    if (!stringValue.has_value() || stringValue->length() == 0) {
+        return std::nullopt;
+    }
+
+    char* end;
+    double value = strtod(stringValue->c_str(), &end);
+    if (*end != '\0') {
+        ALOGW("Property key '%s' has invalid value '%s'.  Expected a double.", key.c_str(),
+              stringValue->c_str());
+        return std::nullopt;
+    }
+    return value;
 }
 
 void PropertyMap::addAll(const PropertyMap* map) {
diff --git a/libs/input/PropertyMap_fuzz.cpp b/libs/input/PropertyMap_fuzz.cpp
old mode 100755
new mode 100644
index d985dc1..6299ca8
--- a/libs/input/PropertyMap_fuzz.cpp
+++ b/libs/input/PropertyMap_fuzz.cpp
@@ -29,8 +29,7 @@
                 },
                 [](FuzzedDataProvider* dataProvider, android::PropertyMap& propertyMap) -> void {
                     std::string key = dataProvider->ConsumeRandomLengthString(MAX_STR_LEN);
-                    std::string out;
-                    propertyMap.tryGetProperty(key, out);
+                    propertyMap.getString(key);
                 },
                 [](FuzzedDataProvider* dataProvider, android::PropertyMap& /*unused*/) -> void {
                     TemporaryFile tf;
diff --git a/libs/input/TfLiteMotionPredictor.cpp b/libs/input/TfLiteMotionPredictor.cpp
index 10510d6..3b061d1 100644
--- a/libs/input/TfLiteMotionPredictor.cpp
+++ b/libs/input/TfLiteMotionPredictor.cpp
@@ -30,6 +30,7 @@
 #include <type_traits>
 #include <utility>
 
+#include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/mapped_file.h>
 #define ATRACE_TAG ATRACE_TAG_INPUT
@@ -60,6 +61,27 @@
 constexpr char OUTPUT_PHI[] = "phi";
 constexpr char OUTPUT_PRESSURE[] = "pressure";
 
+// Ideally, we would just use std::filesystem::exists here, but it requires libc++fs, which causes
+// build issues in other parts of the system.
+#if defined(__ANDROID__)
+bool fileExists(const char* filename) {
+    struct stat buffer;
+    return stat(filename, &buffer) == 0;
+}
+#endif
+
+std::string getModelPath() {
+#if defined(__ANDROID__)
+    static const char* oemModel = "/vendor/etc/motion_predictor_model.fb";
+    if (fileExists(oemModel)) {
+        return oemModel;
+    }
+    return "/system/etc/motion_predictor_model.fb";
+#else
+    return base::GetExecutableDirectory() + "/motion_predictor_model.fb";
+#endif
+}
+
 // A TFLite ErrorReporter that logs to logcat.
 class LoggingErrorReporter : public tflite::ErrorReporter {
 public:
@@ -206,9 +228,9 @@
     mInputOrientation.pushBack(orientation);
 }
 
-std::unique_ptr<TfLiteMotionPredictorModel> TfLiteMotionPredictorModel::create(
-        const char* modelPath) {
-    const int fd = open(modelPath, O_RDONLY);
+std::unique_ptr<TfLiteMotionPredictorModel> TfLiteMotionPredictorModel::create() {
+    const std::string modelPath = getModelPath();
+    android::base::unique_fd fd(open(modelPath.c_str(), O_RDONLY));
     if (fd == -1) {
         PLOG(FATAL) << "Could not read model from " << modelPath;
     }
@@ -223,9 +245,6 @@
     if (!modelBuffer) {
         PLOG(FATAL) << "Failed to mmap model";
     }
-    if (close(fd) == -1) {
-        PLOG(FATAL) << "Failed to close model fd";
-    }
 
     return std::unique_ptr<TfLiteMotionPredictorModel>(
             new TfLiteMotionPredictorModel(std::move(modelBuffer)));
diff --git a/libs/input/TouchVideoFrame.cpp b/libs/input/TouchVideoFrame.cpp
index c9393f4..6d7d561 100644
--- a/libs/input/TouchVideoFrame.cpp
+++ b/libs/input/TouchVideoFrame.cpp
@@ -43,13 +43,13 @@
 void TouchVideoFrame::rotate(ui::Rotation orientation) {
     switch (orientation) {
         case ui::ROTATION_90:
-            rotateQuarterTurn(false /*clockwise*/);
+            rotateQuarterTurn(/*clockwise=*/false);
             break;
         case ui::ROTATION_180:
             rotate180();
             break;
         case ui::ROTATION_270:
-            rotateQuarterTurn(true /*clockwise*/);
+            rotateQuarterTurn(/*clockwise=*/true);
             break;
         case ui::ROTATION_0:
             // No need to rotate if there's no rotation.
diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp
index 3632914..8551e5f 100644
--- a/libs/input/VelocityTracker.cpp
+++ b/libs/input/VelocityTracker.cpp
@@ -157,10 +157,10 @@
 
     std::unique_ptr<VelocityTrackerStrategy> createdStrategy;
     if (mOverrideStrategy != VelocityTracker::Strategy::DEFAULT) {
-        createdStrategy = createStrategy(mOverrideStrategy, isDifferentialAxis /* deltaValues */);
+        createdStrategy = createStrategy(mOverrideStrategy, /*deltaValues=*/isDifferentialAxis);
     } else {
         createdStrategy = createStrategy(DEFAULT_STRATEGY_BY_AXIS.at(axis),
-                                         isDifferentialAxis /* deltaValues */);
+                                         /*deltaValues=*/isDifferentialAxis);
     }
 
     LOG_ALWAYS_FATAL_IF(createdStrategy == nullptr,
@@ -495,7 +495,7 @@
     }
 
     ALOGD_IF(DEBUG_STRATEGY, "  - a=%s",
-             matrixToString(&a[0][0], m, n, false /*rowMajor*/).c_str());
+             matrixToString(&a[0][0], m, n, /*rowMajor=*/false).c_str());
 
     // Apply the Gram-Schmidt process to A to obtain its QR decomposition.
     float q[n][m]; // orthonormal basis, column-major order
@@ -527,8 +527,8 @@
         }
     }
     if (DEBUG_STRATEGY) {
-        ALOGD("  - q=%s", matrixToString(&q[0][0], m, n, false /*rowMajor*/).c_str());
-        ALOGD("  - r=%s", matrixToString(&r[0][0], n, n, true /*rowMajor*/).c_str());
+        ALOGD("  - q=%s", matrixToString(&q[0][0], m, n, /*rowMajor=*/false).c_str());
+        ALOGD("  - r=%s", matrixToString(&r[0][0], n, n, /*rowMajor=*/true).c_str());
 
         // calculate QR, if we factored A correctly then QR should equal A
         float qr[n][m];
@@ -540,7 +540,7 @@
                 }
             }
         }
-        ALOGD("  - qr=%s", matrixToString(&qr[0][0], m, n, false /*rowMajor*/).c_str());
+        ALOGD("  - qr=%s", matrixToString(&qr[0][0], m, n, /*rowMajor=*/false).c_str());
     }
 
     // Solve R B = Qt W Y to find B.  This is easy because R is upper triangular.
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 37faf91..42bdf57 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -43,6 +43,13 @@
         "-Werror",
         "-Wno-unused-parameter",
     ],
+    sanitize: {
+        undefined: true,
+        all_undefined: true,
+        diag: {
+            undefined: true,
+        },
+    },
     shared_libs: [
         "libbase",
         "libbinder",
diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp
index 8a6e983..2132dc1 100644
--- a/libs/input/tests/InputEvent_test.cpp
+++ b/libs/input/tests/InputEvent_test.cpp
@@ -29,6 +29,8 @@
 // Default display id.
 static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
 
+static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
+
 class BaseTest : public testing::Test {
 protected:
     static constexpr std::array<uint8_t, 32> HMAC = {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10,
@@ -235,102 +237,110 @@
     static constexpr float RAW_X_OFFSET = 12;
     static constexpr float RAW_Y_OFFSET = -41.1;
 
+    void SetUp() override;
+
     int32_t mId;
     ui::Transform mTransform;
     ui::Transform mRawTransform;
+    PointerProperties mPointerProperties[2];
+    struct Sample {
+        PointerCoords pointerCoords[2];
+    };
+    std::array<Sample, 3> mSamples{};
 
     void initializeEventWithHistory(MotionEvent* event);
     void assertEqualsEventWithHistory(const MotionEvent* event);
 };
 
-void MotionEventTest::initializeEventWithHistory(MotionEvent* event) {
+void MotionEventTest::SetUp() {
     mId = InputEvent::nextId();
     mTransform.set({X_SCALE, 0, X_OFFSET, 0, Y_SCALE, Y_OFFSET, 0, 0, 1});
     mRawTransform.set({RAW_X_SCALE, 0, RAW_X_OFFSET, 0, RAW_Y_SCALE, RAW_Y_OFFSET, 0, 0, 1});
 
-    PointerProperties pointerProperties[2];
-    pointerProperties[0].clear();
-    pointerProperties[0].id = 1;
-    pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
-    pointerProperties[1].clear();
-    pointerProperties[1].id = 2;
-    pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    mPointerProperties[0].clear();
+    mPointerProperties[0].id = 1;
+    mPointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+    mPointerProperties[1].clear();
+    mPointerProperties[1].id = 2;
+    mPointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
 
-    PointerCoords pointerCoords[2];
-    pointerCoords[0].clear();
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 10);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 11);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 12);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 13);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 14);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 15);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 16);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 17);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 18);
-    pointerCoords[0].isResampled = true;
-    pointerCoords[1].clear();
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 20);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 21);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 22);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 23);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 24);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 25);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 26);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 27);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 28);
+    mSamples[0].pointerCoords[0].clear();
+    mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 10);
+    mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 11);
+    mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 12);
+    mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 13);
+    mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 14);
+    mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 15);
+    mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 16);
+    mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 17);
+    mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 18);
+    mSamples[0].pointerCoords[0].isResampled = true;
+    mSamples[0].pointerCoords[1].clear();
+    mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 20);
+    mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 21);
+    mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 22);
+    mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 23);
+    mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 24);
+    mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 25);
+    mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 26);
+    mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 27);
+    mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 28);
+
+    mSamples[1].pointerCoords[0].clear();
+    mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 110);
+    mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 111);
+    mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 112);
+    mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 113);
+    mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 114);
+    mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 115);
+    mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 116);
+    mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 117);
+    mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 118);
+    mSamples[1].pointerCoords[0].isResampled = true;
+    mSamples[1].pointerCoords[1].clear();
+    mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 120);
+    mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 121);
+    mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 122);
+    mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 123);
+    mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 124);
+    mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 125);
+    mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 126);
+    mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 127);
+    mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 128);
+    mSamples[1].pointerCoords[1].isResampled = true;
+
+    mSamples[2].pointerCoords[0].clear();
+    mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 210);
+    mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 211);
+    mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 212);
+    mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 213);
+    mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 214);
+    mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 215);
+    mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 216);
+    mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 217);
+    mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 218);
+    mSamples[2].pointerCoords[1].clear();
+    mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 220);
+    mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 221);
+    mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 222);
+    mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 223);
+    mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 224);
+    mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 225);
+    mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 226);
+    mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 227);
+    mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 228);
+}
+
+void MotionEventTest::initializeEventWithHistory(MotionEvent* event) {
     event->initialize(mId, 2, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID, HMAC,
                       AMOTION_EVENT_ACTION_MOVE, 0, AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED,
                       AMOTION_EVENT_EDGE_FLAG_TOP, AMETA_ALT_ON, AMOTION_EVENT_BUTTON_PRIMARY,
                       MotionClassification::NONE, mTransform, 2.0f, 2.1f,
                       AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                       mRawTransform, ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME, 2,
-                      pointerProperties, pointerCoords);
-
-    pointerCoords[0].clear();
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 110);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 111);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 112);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 113);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 114);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 115);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 116);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 117);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 118);
-    pointerCoords[0].isResampled = true;
-    pointerCoords[1].clear();
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 120);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 121);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 122);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 123);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 124);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 125);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 126);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 127);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 128);
-    pointerCoords[1].isResampled = true;
-    event->addSample(ARBITRARY_EVENT_TIME + 1, pointerCoords);
-
-    pointerCoords[0].clear();
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 210);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 211);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 212);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 213);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 214);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 215);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 216);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 217);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 218);
-    pointerCoords[1].clear();
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 220);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 221);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 222);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 223);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 224);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 225);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 226);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 227);
-    pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 228);
-    event->addSample(ARBITRARY_EVENT_TIME + 2, pointerCoords);
+                      mPointerProperties, mSamples[0].pointerCoords);
+    event->addSample(ARBITRARY_EVENT_TIME + 1, mSamples[1].pointerCoords);
+    event->addSample(ARBITRARY_EVENT_TIME + 2, mSamples[2].pointerCoords);
 }
 
 void MotionEventTest::assertEqualsEventWithHistory(const MotionEvent* event) {
@@ -367,51 +377,65 @@
     ASSERT_EQ(ARBITRARY_EVENT_TIME + 1, event->getHistoricalEventTime(1));
     ASSERT_EQ(ARBITRARY_EVENT_TIME + 2, event->getEventTime());
 
-    ASSERT_EQ(11, event->getHistoricalRawPointerCoords(0, 0)->getAxisValue(AMOTION_EVENT_AXIS_Y));
-    ASSERT_EQ(21, event->getHistoricalRawPointerCoords(1, 0)->getAxisValue(AMOTION_EVENT_AXIS_Y));
-    ASSERT_EQ(111, event->getHistoricalRawPointerCoords(0, 1)->getAxisValue(AMOTION_EVENT_AXIS_Y));
-    ASSERT_EQ(121, event->getHistoricalRawPointerCoords(1, 1)->getAxisValue(AMOTION_EVENT_AXIS_Y));
-    ASSERT_EQ(211, event->getRawPointerCoords(0)->getAxisValue(AMOTION_EVENT_AXIS_Y));
-    ASSERT_EQ(221, event->getRawPointerCoords(1)->getAxisValue(AMOTION_EVENT_AXIS_Y));
+    // Ensure the underlying PointerCoords are identical.
+    for (int sampleIdx = 0; sampleIdx < 3; sampleIdx++) {
+        for (int pointerIdx = 0; pointerIdx < 2; pointerIdx++) {
+            ASSERT_EQ(mSamples[sampleIdx].pointerCoords[pointerIdx],
+                      event->getSamplePointerCoords()[sampleIdx * 2 + pointerIdx]);
+        }
+    }
 
-    ASSERT_EQ(RAW_Y_OFFSET + 11 * RAW_Y_SCALE,
-              event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 0));
-    ASSERT_EQ(RAW_Y_OFFSET + 21 * RAW_Y_SCALE,
-              event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 0));
-    ASSERT_EQ(RAW_Y_OFFSET + 111 * RAW_Y_SCALE,
-              event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 1));
-    ASSERT_EQ(RAW_Y_OFFSET + 121 * RAW_Y_SCALE,
-              event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 1));
-    ASSERT_EQ(RAW_Y_OFFSET + 211 * RAW_Y_SCALE, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 0));
-    ASSERT_EQ(RAW_Y_OFFSET + 221 * RAW_Y_SCALE, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 1));
+    ASSERT_NEAR(11, event->getHistoricalRawPointerCoords(0, 0)->getAxisValue(AMOTION_EVENT_AXIS_Y),
+                EPSILON);
+    ASSERT_NEAR(21, event->getHistoricalRawPointerCoords(1, 0)->getAxisValue(AMOTION_EVENT_AXIS_Y),
+                EPSILON);
+    ASSERT_NEAR(111, event->getHistoricalRawPointerCoords(0, 1)->getAxisValue(AMOTION_EVENT_AXIS_Y),
+                EPSILON);
+    ASSERT_NEAR(121, event->getHistoricalRawPointerCoords(1, 1)->getAxisValue(AMOTION_EVENT_AXIS_Y),
+                EPSILON);
+    ASSERT_NEAR(211, event->getRawPointerCoords(0)->getAxisValue(AMOTION_EVENT_AXIS_Y), EPSILON);
+    ASSERT_NEAR(221, event->getRawPointerCoords(1)->getAxisValue(AMOTION_EVENT_AXIS_Y), EPSILON);
 
-    ASSERT_EQ(RAW_X_OFFSET + 10 * RAW_X_SCALE, event->getHistoricalRawX(0, 0));
-    ASSERT_EQ(RAW_X_OFFSET + 20 * RAW_X_SCALE, event->getHistoricalRawX(1, 0));
-    ASSERT_EQ(RAW_X_OFFSET + 110 * RAW_X_SCALE, event->getHistoricalRawX(0, 1));
-    ASSERT_EQ(RAW_X_OFFSET + 120 * RAW_X_SCALE, event->getHistoricalRawX(1, 1));
-    ASSERT_EQ(RAW_X_OFFSET + 210 * RAW_X_SCALE, event->getRawX(0));
-    ASSERT_EQ(RAW_X_OFFSET + 220 * RAW_X_SCALE, event->getRawX(1));
+    ASSERT_NEAR(RAW_Y_OFFSET + 11 * RAW_Y_SCALE,
+                event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 0), EPSILON);
+    ASSERT_NEAR(RAW_Y_OFFSET + 21 * RAW_Y_SCALE,
+                event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 0), EPSILON);
+    ASSERT_NEAR(RAW_Y_OFFSET + 111 * RAW_Y_SCALE,
+                event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 1), EPSILON);
+    ASSERT_NEAR(RAW_Y_OFFSET + 121 * RAW_Y_SCALE,
+                event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 1), EPSILON);
+    ASSERT_NEAR(RAW_Y_OFFSET + 211 * RAW_Y_SCALE, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 0),
+                EPSILON);
+    ASSERT_NEAR(RAW_Y_OFFSET + 221 * RAW_Y_SCALE, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 1),
+                EPSILON);
 
-    ASSERT_EQ(RAW_Y_OFFSET + 11 * RAW_Y_SCALE, event->getHistoricalRawY(0, 0));
-    ASSERT_EQ(RAW_Y_OFFSET + 21 * RAW_Y_SCALE, event->getHistoricalRawY(1, 0));
-    ASSERT_EQ(RAW_Y_OFFSET + 111 * RAW_Y_SCALE, event->getHistoricalRawY(0, 1));
-    ASSERT_EQ(RAW_Y_OFFSET + 121 * RAW_Y_SCALE, event->getHistoricalRawY(1, 1));
-    ASSERT_EQ(RAW_Y_OFFSET + 211 * RAW_Y_SCALE, event->getRawY(0));
-    ASSERT_EQ(RAW_Y_OFFSET + 221 * RAW_Y_SCALE, event->getRawY(1));
+    ASSERT_NEAR(RAW_X_OFFSET + 10 * RAW_X_SCALE, event->getHistoricalRawX(0, 0), EPSILON);
+    ASSERT_NEAR(RAW_X_OFFSET + 20 * RAW_X_SCALE, event->getHistoricalRawX(1, 0), EPSILON);
+    ASSERT_NEAR(RAW_X_OFFSET + 110 * RAW_X_SCALE, event->getHistoricalRawX(0, 1), EPSILON);
+    ASSERT_NEAR(RAW_X_OFFSET + 120 * RAW_X_SCALE, event->getHistoricalRawX(1, 1), EPSILON);
+    ASSERT_NEAR(RAW_X_OFFSET + 210 * RAW_X_SCALE, event->getRawX(0), EPSILON);
+    ASSERT_NEAR(RAW_X_OFFSET + 220 * RAW_X_SCALE, event->getRawX(1), EPSILON);
 
-    ASSERT_EQ(X_OFFSET + 10 * X_SCALE, event->getHistoricalX(0, 0));
-    ASSERT_EQ(X_OFFSET + 20 * X_SCALE, event->getHistoricalX(1, 0));
-    ASSERT_EQ(X_OFFSET + 110 * X_SCALE, event->getHistoricalX(0, 1));
-    ASSERT_EQ(X_OFFSET + 120 * X_SCALE, event->getHistoricalX(1, 1));
-    ASSERT_EQ(X_OFFSET + 210 * X_SCALE, event->getX(0));
-    ASSERT_EQ(X_OFFSET + 220 * X_SCALE, event->getX(1));
+    ASSERT_NEAR(RAW_Y_OFFSET + 11 * RAW_Y_SCALE, event->getHistoricalRawY(0, 0), EPSILON);
+    ASSERT_NEAR(RAW_Y_OFFSET + 21 * RAW_Y_SCALE, event->getHistoricalRawY(1, 0), EPSILON);
+    ASSERT_NEAR(RAW_Y_OFFSET + 111 * RAW_Y_SCALE, event->getHistoricalRawY(0, 1), EPSILON);
+    ASSERT_NEAR(RAW_Y_OFFSET + 121 * RAW_Y_SCALE, event->getHistoricalRawY(1, 1), EPSILON);
+    ASSERT_NEAR(RAW_Y_OFFSET + 211 * RAW_Y_SCALE, event->getRawY(0), EPSILON);
+    ASSERT_NEAR(RAW_Y_OFFSET + 221 * RAW_Y_SCALE, event->getRawY(1), EPSILON);
 
-    ASSERT_EQ(Y_OFFSET + 11 * Y_SCALE, event->getHistoricalY(0, 0));
-    ASSERT_EQ(Y_OFFSET + 21 * Y_SCALE, event->getHistoricalY(1, 0));
-    ASSERT_EQ(Y_OFFSET + 111 * Y_SCALE, event->getHistoricalY(0, 1));
-    ASSERT_EQ(Y_OFFSET + 121 * Y_SCALE, event->getHistoricalY(1, 1));
-    ASSERT_EQ(Y_OFFSET + 211 * Y_SCALE, event->getY(0));
-    ASSERT_EQ(Y_OFFSET + 221 * Y_SCALE, event->getY(1));
+    ASSERT_NEAR(X_OFFSET + 10 * X_SCALE, event->getHistoricalX(0, 0), EPSILON);
+    ASSERT_NEAR(X_OFFSET + 20 * X_SCALE, event->getHistoricalX(1, 0), EPSILON);
+    ASSERT_NEAR(X_OFFSET + 110 * X_SCALE, event->getHistoricalX(0, 1), EPSILON);
+    ASSERT_NEAR(X_OFFSET + 120 * X_SCALE, event->getHistoricalX(1, 1), EPSILON);
+    ASSERT_NEAR(X_OFFSET + 210 * X_SCALE, event->getX(0), EPSILON);
+    ASSERT_NEAR(X_OFFSET + 220 * X_SCALE, event->getX(1), EPSILON);
+
+    ASSERT_NEAR(Y_OFFSET + 11 * Y_SCALE, event->getHistoricalY(0, 0), EPSILON);
+    ASSERT_NEAR(Y_OFFSET + 21 * Y_SCALE, event->getHistoricalY(1, 0), EPSILON);
+    ASSERT_NEAR(Y_OFFSET + 111 * Y_SCALE, event->getHistoricalY(0, 1), EPSILON);
+    ASSERT_NEAR(Y_OFFSET + 121 * Y_SCALE, event->getHistoricalY(1, 1), EPSILON);
+    ASSERT_NEAR(Y_OFFSET + 211 * Y_SCALE, event->getY(0), EPSILON);
+    ASSERT_NEAR(Y_OFFSET + 221 * Y_SCALE, event->getY(1), EPSILON);
 
     ASSERT_EQ(12, event->getHistoricalPressure(0, 0));
     ASSERT_EQ(22, event->getHistoricalPressure(1, 0));
@@ -507,7 +531,7 @@
     initializeEventWithHistory(&event);
 
     MotionEvent copy;
-    copy.copyFrom(&event, true /*keepHistory*/);
+    copy.copyFrom(&event, /*keepHistory=*/true);
 
     ASSERT_NO_FATAL_FAILURE(assertEqualsEventWithHistory(&event));
 }
@@ -517,7 +541,7 @@
     initializeEventWithHistory(&event);
 
     MotionEvent copy;
-    copy.copyFrom(&event, false /*keepHistory*/);
+    copy.copyFrom(&event, /*keepHistory=*/false);
 
     ASSERT_EQ(event.getPointerCount(), copy.getPointerCount());
     ASSERT_EQ(0U, copy.getHistorySize());
@@ -550,10 +574,10 @@
     ASSERT_EQ(X_OFFSET * 2, event.getXOffset());
     ASSERT_EQ(Y_OFFSET * 2, event.getYOffset());
 
-    ASSERT_EQ((RAW_X_OFFSET + 210 * RAW_X_SCALE) * 2, event.getRawX(0));
-    ASSERT_EQ((RAW_Y_OFFSET + 211 * RAW_Y_SCALE) * 2, event.getRawY(0));
-    ASSERT_EQ((X_OFFSET + 210 * X_SCALE) * 2, event.getX(0));
-    ASSERT_EQ((Y_OFFSET + 211 * Y_SCALE) * 2, event.getY(0));
+    ASSERT_NEAR((RAW_X_OFFSET + 210 * RAW_X_SCALE) * 2, event.getRawX(0), EPSILON);
+    ASSERT_NEAR((RAW_Y_OFFSET + 211 * RAW_Y_SCALE) * 2, event.getRawY(0), EPSILON);
+    ASSERT_NEAR((X_OFFSET + 210 * X_SCALE) * 2, event.getX(0), EPSILON);
+    ASSERT_NEAR((Y_OFFSET + 211 * Y_SCALE) * 2, event.getY(0), EPSILON);
     ASSERT_EQ(212, event.getPressure(0));
     ASSERT_EQ(213, event.getSize(0));
     ASSERT_EQ(214 * 2, event.getTouchMajor(0));
@@ -618,12 +642,12 @@
     }
     MotionEvent event;
     ui::Transform identityTransform;
-    event.initialize(InputEvent::nextId(), 0 /*deviceId*/, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID,
-                     INVALID_HMAC, AMOTION_EVENT_ACTION_MOVE, 0 /*actionButton*/, 0 /*flags*/,
-                     AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0 /*buttonState*/,
-                     MotionClassification::NONE, identityTransform, 0 /*xPrecision*/,
-                     0 /*yPrecision*/, 3 + RADIUS /*xCursorPosition*/, 2 /*yCursorPosition*/,
-                     identityTransform, 0 /*downTime*/, 0 /*eventTime*/, pointerCount,
+    event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID,
+                     INVALID_HMAC, AMOTION_EVENT_ACTION_MOVE, /*actionButton=*/0, /*flags=*/0,
+                     AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, /*buttonState=*/0,
+                     MotionClassification::NONE, identityTransform, /*xPrecision=*/0,
+                     /*yPrecision=*/0, /*xCursorPosition=*/3 + RADIUS, /*yCursorPosition=*/2,
+                     identityTransform, /*downTime=*/0, /*eventTime=*/0, pointerCount,
                      pointerProperties, pointerCoords);
     float originalRawX = 0 + 3;
     float originalRawY = -RADIUS + 2;
@@ -791,18 +815,18 @@
 
     // The x and y axes should have the window transform applied.
     const auto newPoint = transform.transform(60, 100);
-    ASSERT_EQ(newPoint.x, event.getX(0));
-    ASSERT_EQ(newPoint.y, event.getY(0));
+    ASSERT_NEAR(newPoint.x, event.getX(0), EPSILON);
+    ASSERT_NEAR(newPoint.y, event.getY(0), EPSILON);
 
     // The raw values should have the display transform applied.
     const auto raw = rawTransform.transform(60, 100);
-    ASSERT_EQ(raw.x, event.getRawX(0));
-    ASSERT_EQ(raw.y, event.getRawY(0));
+    ASSERT_NEAR(raw.x, event.getRawX(0), EPSILON);
+    ASSERT_NEAR(raw.y, event.getRawY(0), EPSILON);
 
     // Relative values should have the window transform applied without any translation.
     const auto rel = transformWithoutTranslation(transform, 42, 96);
-    ASSERT_EQ(rel.x, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0));
-    ASSERT_EQ(rel.y, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0));
+    ASSERT_NEAR(rel.x, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0), EPSILON);
+    ASSERT_NEAR(rel.y, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0), EPSILON);
 }
 
 TEST_F(MotionEventTest, Initialize_SetsClassification) {
@@ -824,12 +848,12 @@
 
     ui::Transform identityTransform;
     for (MotionClassification classification : classifications) {
-        event.initialize(InputEvent::nextId(), 0 /*deviceId*/, AINPUT_SOURCE_TOUCHSCREEN,
+        event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_TOUCHSCREEN,
                          DISPLAY_ID, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, 0, 0,
                          AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0, classification,
                          identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                         AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 0 /*downTime*/,
-                         0 /*eventTime*/, pointerCount, pointerProperties, pointerCoords);
+                         AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, /*downTime=*/0,
+                         /*eventTime=*/0, pointerCount, pointerProperties, pointerCoords);
         ASSERT_EQ(classification, event.getClassification());
     }
 }
@@ -846,11 +870,11 @@
     }
 
     ui::Transform identityTransform;
-    event.initialize(InputEvent::nextId(), 0 /*deviceId*/, AINPUT_SOURCE_MOUSE, DISPLAY_ID,
+    event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_MOUSE, DISPLAY_ID,
                      INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, 0, 0, AMOTION_EVENT_EDGE_FLAG_NONE,
                      AMETA_NONE, 0, MotionClassification::NONE, identityTransform, 0, 0,
-                     280 /*xCursorPosition*/, 540 /*yCursorPosition*/, identityTransform,
-                     0 /*downTime*/, 0 /*eventTime*/, pointerCount, pointerProperties,
+                     /*xCursorPosition=*/280, /*yCursorPosition=*/540, identityTransform,
+                     /*downTime=*/0, /*eventTime=*/0, pointerCount, pointerProperties,
                      pointerCoords);
     event.offsetLocation(20, 60);
     ASSERT_EQ(280, event.getRawXCursorPosition());
@@ -869,4 +893,42 @@
     ASSERT_EQ(4, event.getYCursorPosition());
 }
 
+TEST_F(MotionEventTest, CoordinatesAreRoundedAppropriately) {
+    // These are specifically integral values, since we are testing for rounding.
+    const vec2 EXPECTED{400.f, 700.f};
+
+    // Pick a transform such that transforming the point with its inverse and bringing that
+    // back to the original coordinate space results in a non-zero error amount due to the
+    // nature of floating point arithmetics. This can happen when the display is scaled.
+    // For example, the 'adb shell wm size' command can be used to set an override for the
+    // logical display size, which could result in the display being scaled.
+    constexpr float scale = 720.f / 1080.f;
+    ui::Transform transform;
+    transform.set(scale, 0, 0, scale);
+    ASSERT_NE(EXPECTED, transform.transform(transform.inverse().transform(EXPECTED)));
+
+    // Store the inverse-transformed values in the motion event.
+    const vec2 rawCoords = transform.inverse().transform(EXPECTED);
+    PointerCoords pc{};
+    pc.setAxisValue(AMOTION_EVENT_AXIS_X, rawCoords.x);
+    pc.setAxisValue(AMOTION_EVENT_AXIS_Y, rawCoords.y);
+    PointerProperties pp{};
+    MotionEvent event;
+    event.initialize(InputEvent::nextId(), 2, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID, HMAC,
+                     AMOTION_EVENT_ACTION_MOVE, 0, AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED,
+                     AMOTION_EVENT_EDGE_FLAG_TOP, AMETA_ALT_ON, AMOTION_EVENT_BUTTON_PRIMARY,
+                     MotionClassification::NONE, transform, 2.0f, 2.1f, rawCoords.x, rawCoords.y,
+                     transform, ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME, 1, &pp, &pc);
+
+    // When using the getters from the MotionEvent to obtain the coordinates, the transformed
+    // values should be rounded by an appropriate amount so that they now precisely equal the
+    // original coordinates.
+    ASSERT_EQ(EXPECTED.x, event.getX(0));
+    ASSERT_EQ(EXPECTED.y, event.getY(0));
+    ASSERT_EQ(EXPECTED.x, event.getRawX(0));
+    ASSERT_EQ(EXPECTED.y, event.getRawY(0));
+    ASSERT_EQ(EXPECTED.x, event.getXCursorPosition());
+    ASSERT_EQ(EXPECTED.y, event.getYCursorPosition());
+}
+
 } // namespace android
diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp
index 70e4fda..5d8b970 100644
--- a/libs/input/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp
@@ -25,6 +25,8 @@
 
 namespace android {
 
+constexpr static float EPSILON = MotionEvent::ROUNDING_PRECISION;
+
 class InputPublisherAndConsumerTest : public testing::Test {
 protected:
     std::shared_ptr<InputChannel> mServerChannel, mClientChannel;
@@ -90,7 +92,7 @@
 
     uint32_t consumeSeq;
     InputEvent* event;
-    status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event);
+    status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event);
     ASSERT_EQ(OK, status)
             << "consumer consume should return OK";
 
@@ -199,7 +201,7 @@
 
     uint32_t consumeSeq;
     InputEvent* event;
-    status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event);
+    status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event);
     ASSERT_EQ(OK, status)
             << "consumer consume should return OK";
 
@@ -226,10 +228,10 @@
     EXPECT_EQ(yOffset, motionEvent->getYOffset());
     EXPECT_EQ(xPrecision, motionEvent->getXPrecision());
     EXPECT_EQ(yPrecision, motionEvent->getYPrecision());
-    EXPECT_EQ(xCursorPosition, motionEvent->getRawXCursorPosition());
-    EXPECT_EQ(yCursorPosition, motionEvent->getRawYCursorPosition());
-    EXPECT_EQ(xCursorPosition * xScale + xOffset, motionEvent->getXCursorPosition());
-    EXPECT_EQ(yCursorPosition * yScale + yOffset, motionEvent->getYCursorPosition());
+    EXPECT_NEAR(xCursorPosition, motionEvent->getRawXCursorPosition(), EPSILON);
+    EXPECT_NEAR(yCursorPosition, motionEvent->getRawYCursorPosition(), EPSILON);
+    EXPECT_NEAR(xCursorPosition * xScale + xOffset, motionEvent->getXCursorPosition(), EPSILON);
+    EXPECT_NEAR(yCursorPosition * yScale + yOffset, motionEvent->getYCursorPosition(), EPSILON);
     EXPECT_EQ(rawTransform, motionEvent->getRawTransform());
     EXPECT_EQ(downTime, motionEvent->getDownTime());
     EXPECT_EQ(eventTime, motionEvent->getEventTime());
@@ -242,10 +244,12 @@
         EXPECT_EQ(pointerProperties[i].toolType, motionEvent->getToolType(i));
 
         const auto& pc = pointerCoords[i];
-        EXPECT_EQ(pc.getX() * rawXScale + rawXOffset, motionEvent->getRawX(i));
-        EXPECT_EQ(pc.getY() * rawYScale + rawYOffset, motionEvent->getRawY(i));
-        EXPECT_EQ(pc.getX() * xScale + xOffset, motionEvent->getX(i));
-        EXPECT_EQ(pc.getY() * yScale + yOffset, motionEvent->getY(i));
+        EXPECT_EQ(pc, motionEvent->getSamplePointerCoords()[i]);
+
+        EXPECT_NEAR(pc.getX() * rawXScale + rawXOffset, motionEvent->getRawX(i), EPSILON);
+        EXPECT_NEAR(pc.getY() * rawYScale + rawYOffset, motionEvent->getRawY(i), EPSILON);
+        EXPECT_NEAR(pc.getX() * xScale + xOffset, motionEvent->getX(i), EPSILON);
+        EXPECT_NEAR(pc.getY() * yScale + yOffset, motionEvent->getY(i), EPSILON);
         EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), motionEvent->getPressure(i));
         EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_SIZE), motionEvent->getSize(i));
         EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), motionEvent->getTouchMajor(i));
@@ -290,7 +294,7 @@
 
     uint32_t consumeSeq;
     InputEvent* event;
-    status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event);
+    status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event);
     ASSERT_EQ(OK, status) << "consumer consume should return OK";
 
     ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event";
@@ -331,7 +335,7 @@
 
     uint32_t consumeSeq;
     InputEvent* event;
-    status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event);
+    status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event);
     ASSERT_EQ(OK, status) << "consumer consume should return OK";
 
     ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event";
@@ -373,7 +377,7 @@
 
     uint32_t consumeSeq;
     InputEvent* event;
-    status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event);
+    status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event);
     ASSERT_EQ(OK, status) << "consumer consume should return OK";
 
     ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event";
@@ -415,7 +419,7 @@
 
     uint32_t consumeSeq;
     InputEvent* event;
-    status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event);
+    status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event);
     ASSERT_EQ(OK, status) << "consumer consume should return OK";
 
     ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event";
diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp
index ce87c86..c61efbf 100644
--- a/libs/input/tests/MotionPredictor_test.cpp
+++ b/libs/input/tests/MotionPredictor_test.cpp
@@ -30,13 +30,6 @@
 using ::testing::SizeIs;
 using ::testing::UnorderedElementsAre;
 
-const char MODEL_PATH[] =
-#if defined(__ANDROID__)
-        "/system/etc/motion_predictor_model.fb";
-#else
-        "motion_predictor_model.fb";
-#endif
-
 constexpr int32_t DOWN = AMOTION_EVENT_ACTION_DOWN;
 constexpr int32_t MOVE = AMOTION_EVENT_ACTION_MOVE;
 constexpr int32_t UP = AMOTION_EVENT_ACTION_UP;
@@ -73,83 +66,74 @@
 }
 
 TEST(MotionPredictorTest, IsPredictionAvailable) {
-    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, MODEL_PATH,
+    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
                               []() { return true /*enable prediction*/; });
     ASSERT_TRUE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_STYLUS));
     ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_TOUCHSCREEN));
 }
 
 TEST(MotionPredictorTest, Offset) {
-    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/1, MODEL_PATH,
+    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/1,
                               []() { return true /*enable prediction*/; });
     predictor.record(getMotionEvent(DOWN, 0, 1, 30ms));
     predictor.record(getMotionEvent(MOVE, 0, 2, 35ms));
-    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40 * NSEC_PER_MSEC);
-    ASSERT_EQ(1u, predicted.size());
-    ASSERT_GE(predicted[0]->getEventTime(), 41);
+    std::unique_ptr<MotionEvent> predicted = predictor.predict(40 * NSEC_PER_MSEC);
+    ASSERT_NE(nullptr, predicted);
+    ASSERT_GE(predicted->getEventTime(), 41);
 }
 
 TEST(MotionPredictorTest, FollowsGesture) {
-    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, MODEL_PATH,
+    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
                               []() { return true /*enable prediction*/; });
 
     // MOVE without a DOWN is ignored.
     predictor.record(getMotionEvent(MOVE, 1, 3, 10ms));
-    EXPECT_THAT(predictor.predict(20 * NSEC_PER_MSEC), IsEmpty());
+    EXPECT_EQ(nullptr, predictor.predict(20 * NSEC_PER_MSEC));
 
     predictor.record(getMotionEvent(DOWN, 2, 5, 20ms));
     predictor.record(getMotionEvent(MOVE, 2, 7, 30ms));
     predictor.record(getMotionEvent(MOVE, 3, 9, 40ms));
-    EXPECT_THAT(predictor.predict(50 * NSEC_PER_MSEC), SizeIs(1));
+    EXPECT_NE(nullptr, predictor.predict(50 * NSEC_PER_MSEC));
 
     predictor.record(getMotionEvent(UP, 4, 11, 50ms));
-    EXPECT_THAT(predictor.predict(20 * NSEC_PER_MSEC), IsEmpty());
+    EXPECT_EQ(nullptr, predictor.predict(20 * NSEC_PER_MSEC));
 }
 
-TEST(MotionPredictorTest, MultipleDevicesTracked) {
-    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, MODEL_PATH,
+TEST(MotionPredictorTest, MultipleDevicesNotSupported) {
+    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
                               []() { return true /*enable prediction*/; });
 
-    predictor.record(getMotionEvent(DOWN, 1, 3, 0ms, /*deviceId=*/0));
-    predictor.record(getMotionEvent(MOVE, 1, 3, 10ms, /*deviceId=*/0));
-    predictor.record(getMotionEvent(MOVE, 2, 5, 20ms, /*deviceId=*/0));
-    predictor.record(getMotionEvent(MOVE, 3, 7, 30ms, /*deviceId=*/0));
+    ASSERT_TRUE(predictor.record(getMotionEvent(DOWN, 1, 3, 0ms, /*deviceId=*/0)).ok());
+    ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 1, 3, 10ms, /*deviceId=*/0)).ok());
+    ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 2, 5, 20ms, /*deviceId=*/0)).ok());
+    ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 3, 7, 30ms, /*deviceId=*/0)).ok());
 
-    predictor.record(getMotionEvent(DOWN, 100, 300, 0ms, /*deviceId=*/1));
-    predictor.record(getMotionEvent(MOVE, 100, 300, 10ms, /*deviceId=*/1));
-    predictor.record(getMotionEvent(MOVE, 200, 500, 20ms, /*deviceId=*/1));
-    predictor.record(getMotionEvent(MOVE, 300, 700, 30ms, /*deviceId=*/1));
+    ASSERT_FALSE(predictor.record(getMotionEvent(DOWN, 100, 300, 40ms, /*deviceId=*/1)).ok());
+    ASSERT_FALSE(predictor.record(getMotionEvent(MOVE, 100, 300, 50ms, /*deviceId=*/1)).ok());
+}
 
-    {
-        std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40 * NSEC_PER_MSEC);
-        ASSERT_EQ(2u, predicted.size());
+TEST(MotionPredictorTest, IndividualGesturesFromDifferentDevicesAreSupported) {
+    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
+                              []() { return true /*enable prediction*/; });
 
-        // Order of the returned vector is not guaranteed.
-        std::vector<int32_t> seenDeviceIds;
-        for (const auto& prediction : predicted) {
-            seenDeviceIds.push_back(prediction->getDeviceId());
-        }
-        EXPECT_THAT(seenDeviceIds, UnorderedElementsAre(0, 1));
-    }
+    ASSERT_TRUE(predictor.record(getMotionEvent(DOWN, 1, 3, 0ms, /*deviceId=*/0)).ok());
+    ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 1, 3, 10ms, /*deviceId=*/0)).ok());
+    ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 2, 5, 20ms, /*deviceId=*/0)).ok());
+    ASSERT_TRUE(predictor.record(getMotionEvent(UP, 2, 5, 30ms, /*deviceId=*/0)).ok());
 
-    // End the gesture for device 0.
-    predictor.record(getMotionEvent(UP, 4, 9, 40ms, /*deviceId=*/0));
-    predictor.record(getMotionEvent(MOVE, 400, 900, 40ms, /*deviceId=*/1));
-
-    {
-        std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40 * NSEC_PER_MSEC);
-        ASSERT_EQ(1u, predicted.size());
-        ASSERT_EQ(predicted[0]->getDeviceId(), 1);
-    }
+    // Now, send a gesture from a different device. Since we have no active gesture, the new gesture
+    // should be processed correctly.
+    ASSERT_TRUE(predictor.record(getMotionEvent(DOWN, 100, 300, 40ms, /*deviceId=*/1)).ok());
+    ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 100, 300, 50ms, /*deviceId=*/1)).ok());
 }
 
 TEST(MotionPredictorTest, FlagDisablesPrediction) {
-    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, MODEL_PATH,
+    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
                               []() { return false /*disable prediction*/; });
     predictor.record(getMotionEvent(DOWN, 0, 1, 30ms));
     predictor.record(getMotionEvent(MOVE, 0, 1, 35ms));
-    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40 * NSEC_PER_MSEC);
-    ASSERT_EQ(0u, predicted.size());
+    std::unique_ptr<MotionEvent> predicted = predictor.predict(40 * NSEC_PER_MSEC);
+    ASSERT_EQ(nullptr, predicted);
     ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_STYLUS));
     ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_TOUCHSCREEN));
 }
diff --git a/libs/input/tests/TfLiteMotionPredictor_test.cpp b/libs/input/tests/TfLiteMotionPredictor_test.cpp
index 454f2aa..6e76ac1 100644
--- a/libs/input/tests/TfLiteMotionPredictor_test.cpp
+++ b/libs/input/tests/TfLiteMotionPredictor_test.cpp
@@ -21,7 +21,6 @@
 #include <iterator>
 #include <string>
 
-#include <android-base/file.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <input/TfLiteMotionPredictor.h>
@@ -33,14 +32,6 @@
 using ::testing::ElementsAre;
 using ::testing::FloatNear;
 
-std::string getModelPath() {
-#if defined(__ANDROID__)
-    return "/system/etc/motion_predictor_model.fb";
-#else
-    return base::GetExecutableDirectory() + "/motion_predictor_model.fb";
-#endif
-}
-
 TEST(TfLiteMotionPredictorTest, BuffersReadiness) {
     TfLiteMotionPredictorBuffers buffers(/*inputLength=*/5);
     ASSERT_FALSE(buffers.isReady());
@@ -92,8 +83,7 @@
 }
 
 TEST(TfLiteMotionPredictorTest, BuffersCopyTo) {
-    std::unique_ptr<TfLiteMotionPredictorModel> model =
-            TfLiteMotionPredictorModel::create(getModelPath().c_str());
+    std::unique_ptr<TfLiteMotionPredictorModel> model = TfLiteMotionPredictorModel::create();
     TfLiteMotionPredictorBuffers buffers(model->inputLength());
 
     buffers.pushSample(/*timestamp=*/1,
@@ -137,8 +127,7 @@
 }
 
 TEST(TfLiteMotionPredictorTest, ModelInputOutputLength) {
-    std::unique_ptr<TfLiteMotionPredictorModel> model =
-            TfLiteMotionPredictorModel::create(getModelPath().c_str());
+    std::unique_ptr<TfLiteMotionPredictorModel> model = TfLiteMotionPredictorModel::create();
     ASSERT_GT(model->inputLength(), 0u);
 
     const int inputLength = model->inputLength();
@@ -155,8 +144,7 @@
 }
 
 TEST(TfLiteMotionPredictorTest, ModelOutput) {
-    std::unique_ptr<TfLiteMotionPredictorModel> model =
-            TfLiteMotionPredictorModel::create(getModelPath().c_str());
+    std::unique_ptr<TfLiteMotionPredictorModel> model = TfLiteMotionPredictorModel::create();
     TfLiteMotionPredictorBuffers buffers(model->inputLength());
 
     buffers.pushSample(/*timestamp=*/1, {.position = {.x = 100, .y = 200}, .pressure = 0.2});
diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp
index d01258c..7cb9526 100644
--- a/libs/input/tests/TouchResampling_test.cpp
+++ b/libs/input/tests/TouchResampling_test.cpp
@@ -56,7 +56,7 @@
 
         mPublisher = std::make_unique<InputPublisher>(std::move(serverChannel));
         mConsumer = std::make_unique<InputConsumer>(std::move(clientChannel),
-                                                    true /* enableTouchResampling */);
+                                                    /*enableTouchResampling=*/true);
     }
 
     status_t publishSimpleMotionEventWithCoords(int32_t action, nsecs_t eventTime,
@@ -79,11 +79,11 @@
     if (action == AMOTION_EVENT_ACTION_DOWN && eventTime != 0) {
         ADD_FAILURE() << "Downtime should be equal to 0 (hardcoded for convenience)";
     }
-    return mPublisher->publishMotionEvent(mSeq++, InputEvent::nextId(), 1 /*deviceId*/,
-                                          AINPUT_SOURCE_TOUCHSCREEN, 0 /*displayId*/, INVALID_HMAC,
-                                          action, 0 /*actionButton*/, 0 /*flags*/, 0 /*edgeFlags*/,
-                                          AMETA_NONE, 0 /*buttonState*/, MotionClassification::NONE,
-                                          identityTransform, 0 /*xPrecision*/, 0 /*yPrecision*/,
+    return mPublisher->publishMotionEvent(mSeq++, InputEvent::nextId(), /*deviceId=*/1,
+                                          AINPUT_SOURCE_TOUCHSCREEN, /*displayId=*/0, INVALID_HMAC,
+                                          action, /*actionButton=*/0, /*flags=*/0, /*edgeFlags=*/0,
+                                          AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE,
+                                          identityTransform, /*xPrecision=*/0, /*yPrecision=*/0,
                                           AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                           AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
                                           downTime, eventTime, properties.size(), properties.data(),
@@ -161,7 +161,7 @@
     uint32_t consumeSeq;
     InputEvent* event;
 
-    status_t status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, frameTime.count(),
+    status_t status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, frameTime.count(),
                                          &consumeSeq, &event);
     ASSERT_EQ(OK, status);
     MotionEvent* motionEvent = static_cast<MotionEvent*>(event);
diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp
index c6ad3a2..0277579 100644
--- a/libs/input/tests/VelocityTracker_test.cpp
+++ b/libs/input/tests/VelocityTracker_test.cpp
@@ -159,13 +159,13 @@
 
         MotionEvent event;
         ui::Transform identityTransform;
-        event.initialize(InputEvent::nextId(), 5 /*deviceId*/, AINPUT_SOURCE_ROTARY_ENCODER,
+        event.initialize(InputEvent::nextId(), /*deviceId=*/5, AINPUT_SOURCE_ROTARY_ENCODER,
                          ADISPLAY_ID_NONE, INVALID_HMAC, AMOTION_EVENT_ACTION_SCROLL,
-                         0 /*actionButton*/, 0 /*flags*/, AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE,
-                         0 /*buttonState*/, MotionClassification::NONE, identityTransform,
-                         0 /*xPrecision*/, 0 /*yPrecision*/, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                         AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 0 /*downTime*/,
-                         timeStamp.count(), 1 /*pointerCount*/, properties, coords);
+                         /*actionButton=*/0, /*flags=*/0, AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE,
+                         /*buttonState=*/0, MotionClassification::NONE, identityTransform,
+                         /*xPrecision=*/0, /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                         AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, /*downTime=*/0,
+                         timeStamp.count(), /*pointerCount=*/1, properties, coords);
 
         events.emplace_back(event);
     }
@@ -219,12 +219,12 @@
 
         MotionEvent event;
         ui::Transform identityTransform;
-        event.initialize(InputEvent::nextId(), 0 /*deviceId*/, AINPUT_SOURCE_TOUCHSCREEN,
-                         DISPLAY_ID, INVALID_HMAC, action, 0 /*actionButton*/, 0 /*flags*/,
-                         AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0 /*buttonState*/,
-                         MotionClassification::NONE, identityTransform, 0 /*xPrecision*/,
-                         0 /*yPrecision*/, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                         AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 0 /*downTime*/,
+        event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_TOUCHSCREEN,
+                         DISPLAY_ID, INVALID_HMAC, action, /*actionButton=*/0, /*flags=*/0,
+                         AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, /*buttonState=*/0,
+                         MotionClassification::NONE, identityTransform, /*xPrecision=*/0,
+                         /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                         AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, /*downTime=*/0,
                          entry.eventTime.count(), pointerCount, properties, coords);
 
         events.emplace_back(event);
@@ -341,23 +341,23 @@
 TEST_F(VelocityTrackerTest, TestComputedVelocity) {
     VelocityTracker::ComputedVelocity computedVelocity;
 
-    computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, 0 /*id*/, 200 /*velocity*/);
-    computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, 26U /*id*/, 400 /*velocity*/);
-    computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, 27U /*id*/, 650 /*velocity*/);
-    computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, MAX_POINTER_ID, 750 /*velocity*/);
-    computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, 0 /*id*/, 1000 /*velocity*/);
-    computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, 26U /*id*/, 2000 /*velocity*/);
-    computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, 27U /*id*/, 3000 /*velocity*/);
-    computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, MAX_POINTER_ID, 4000 /*velocity*/);
+    computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, /*id=*/0, /*velocity=*/200);
+    computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, /*id=*/26U, /*velocity=*/400);
+    computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, /*id=*/27U, /*velocity=*/650);
+    computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, MAX_POINTER_ID, /*velocity=*/750);
+    computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, /*id=*/0, /*velocity=*/1000);
+    computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, /*id=*/26U, /*velocity=*/2000);
+    computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, /*id=*/27U, /*velocity=*/3000);
+    computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, MAX_POINTER_ID, /*velocity=*/4000);
 
     // Check the axes/indices with velocity.
-    EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, 0U /*id*/)), 200);
-    EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, 26U /*id*/)), 400);
-    EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, 27U /*id*/)), 650);
+    EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, /*id=*/0U)), 200);
+    EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, /*id=*/26U)), 400);
+    EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, /*id=*/27U)), 650);
     EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, MAX_POINTER_ID)), 750);
-    EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, 0U /*id*/)), 1000);
-    EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, 26U /*id*/)), 2000);
-    EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, 27U /*id*/)), 3000);
+    EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, /*id=*/0U)), 1000);
+    EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, /*id=*/26U)), 2000);
+    EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, /*id=*/27U)), 3000);
     EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, MAX_POINTER_ID)), 4000);
     for (uint32_t id = 0; id <= MAX_POINTER_ID; id++) {
         // Since no data was added for AXIS_SCROLL, expect empty value for the axis for any id.
@@ -436,17 +436,17 @@
 
     float maxFloat = std::numeric_limits<float>::max();
     VelocityTracker::ComputedVelocity computedVelocity;
-    computedVelocity = vt.getComputedVelocity(1000 /* units */, maxFloat);
+    computedVelocity = vt.getComputedVelocity(/*units=*/1000, maxFloat);
     checkVelocity(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)),
                   764.345703);
 
     // Expect X velocity to be scaled with respective to provided units.
-    computedVelocity = vt.getComputedVelocity(1000000 /* units */, maxFloat);
+    computedVelocity = vt.getComputedVelocity(/*units=*/1000000, maxFloat);
     checkVelocity(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)),
                   764345.703);
 
     // Expect X velocity to be clamped by provided max velocity.
-    computedVelocity = vt.getComputedVelocity(1000000 /* units */, 1000);
+    computedVelocity = vt.getComputedVelocity(/*units=*/1000000, 1000);
     checkVelocity(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)), 1000);
 
     // All 0 data for Y; expect 0 velocity.
diff --git a/libs/input/tests/VerifiedInputEvent_test.cpp b/libs/input/tests/VerifiedInputEvent_test.cpp
index f2b59ea..277d74d 100644
--- a/libs/input/tests/VerifiedInputEvent_test.cpp
+++ b/libs/input/tests/VerifiedInputEvent_test.cpp
@@ -23,10 +23,10 @@
 
 static KeyEvent getKeyEventWithFlags(int32_t flags) {
     KeyEvent event;
-    event.initialize(InputEvent::nextId(), 2 /*deviceId*/, AINPUT_SOURCE_GAMEPAD,
+    event.initialize(InputEvent::nextId(), /*deviceId=*/2, AINPUT_SOURCE_GAMEPAD,
                      ADISPLAY_ID_DEFAULT, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, flags,
-                     AKEYCODE_BUTTON_X, 121 /*scanCode*/, AMETA_ALT_ON, 1 /*repeatCount*/,
-                     1000 /*downTime*/, 2000 /*eventTime*/);
+                     AKEYCODE_BUTTON_X, /*scanCode=*/121, AMETA_ALT_ON, /*repeatCount=*/1,
+                     /*downTime=*/1000, /*eventTime=*/2000);
     return event;
 }
 
@@ -44,12 +44,12 @@
     ui::Transform transform;
     transform.set({2, 0, 4, 0, 3, 5, 0, 0, 1});
     ui::Transform identity;
-    event.initialize(InputEvent::nextId(), 0 /*deviceId*/, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_DEFAULT,
-                     INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, 0 /*actionButton*/, flags,
-                     AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0 /*buttonState*/,
-                     MotionClassification::NONE, transform, 0.1 /*xPrecision*/, 0.2 /*yPrecision*/,
-                     280 /*xCursorPosition*/, 540 /*yCursorPosition*/, identity, 100 /*downTime*/,
-                     200 /*eventTime*/, pointerCount, pointerProperties, pointerCoords);
+    event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_DEFAULT,
+                     INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0, flags,
+                     AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, /*buttonState=*/0,
+                     MotionClassification::NONE, transform, /*xPrecision=*/0.1, /*yPrecision=*/0.2,
+                     /*xCursorPosition=*/280, /*yCursorPosition=*/540, identity, /*downTime=*/100,
+                     /*eventTime=*/200, pointerCount, pointerProperties, pointerCoords);
     return event;
 }
 
diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp
index 0c03ede..b470f35 100644
--- a/libs/jpegrecoverymap/Android.bp
+++ b/libs/jpegrecoverymap/Android.bp
@@ -29,6 +29,7 @@
     local_include_dirs: ["include"],
 
     srcs: [
+        "icc.cpp",
         "jpegr.cpp",
         "recoverymapmath.cpp",
         "jpegrutils.cpp",
@@ -43,8 +44,6 @@
         "liblog",
         "libutils",
     ],
-
-    static_libs: ["libskia"],
 }
 
 cc_library {
diff --git a/libs/jpegrecoverymap/OWNERS b/libs/jpegrecoverymap/OWNERS
index 133af5b..6ace354 100644
--- a/libs/jpegrecoverymap/OWNERS
+++ b/libs/jpegrecoverymap/OWNERS
@@ -1,4 +1,3 @@
 arifdikici@google.com
-deakin@google.com
 dichenzhang@google.com
 kyslov@google.com
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/icc.cpp b/libs/jpegrecoverymap/icc.cpp
new file mode 100644
index 0000000..5412cb1
--- /dev/null
+++ b/libs/jpegrecoverymap/icc.cpp
@@ -0,0 +1,584 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jpegrecoverymap/icc.h>
+#include <jpegrecoverymap/recoverymapmath.h>
+#include <vector>
+#include <utils/Log.h>
+
+#ifndef FLT_MAX
+#define FLT_MAX 0x1.fffffep127f
+#endif
+
+namespace android::jpegrecoverymap {
+static void Matrix3x3_apply(const Matrix3x3* m, float* x) {
+    float y0 = x[0] * m->vals[0][0] + x[1] * m->vals[0][1] + x[2] * m->vals[0][2];
+    float y1 = x[0] * m->vals[1][0] + x[1] * m->vals[1][1] + x[2] * m->vals[1][2];
+    float y2 = x[0] * m->vals[2][0] + x[1] * m->vals[2][1] + x[2] * m->vals[2][2];
+    x[0] = y0;
+    x[1] = y1;
+    x[2] = y2;
+}
+
+bool Matrix3x3_invert(const Matrix3x3* src, Matrix3x3* dst) {
+    double a00 = src->vals[0][0],
+           a01 = src->vals[1][0],
+           a02 = src->vals[2][0],
+           a10 = src->vals[0][1],
+           a11 = src->vals[1][1],
+           a12 = src->vals[2][1],
+           a20 = src->vals[0][2],
+           a21 = src->vals[1][2],
+           a22 = src->vals[2][2];
+
+    double b0 = a00*a11 - a01*a10,
+           b1 = a00*a12 - a02*a10,
+           b2 = a01*a12 - a02*a11,
+           b3 = a20,
+           b4 = a21,
+           b5 = a22;
+
+    double determinant = b0*b5
+                       - b1*b4
+                       + b2*b3;
+
+    if (determinant == 0) {
+        return false;
+    }
+
+    double invdet = 1.0 / determinant;
+    if (invdet > +FLT_MAX || invdet < -FLT_MAX || !isfinitef_((float)invdet)) {
+        return false;
+    }
+
+    b0 *= invdet;
+    b1 *= invdet;
+    b2 *= invdet;
+    b3 *= invdet;
+    b4 *= invdet;
+    b5 *= invdet;
+
+    dst->vals[0][0] = (float)( a11*b5 - a12*b4 );
+    dst->vals[1][0] = (float)( a02*b4 - a01*b5 );
+    dst->vals[2][0] = (float)(        +     b2 );
+    dst->vals[0][1] = (float)( a12*b3 - a10*b5 );
+    dst->vals[1][1] = (float)( a00*b5 - a02*b3 );
+    dst->vals[2][1] = (float)(        -     b1 );
+    dst->vals[0][2] = (float)( a10*b4 - a11*b3 );
+    dst->vals[1][2] = (float)( a01*b3 - a00*b4 );
+    dst->vals[2][2] = (float)(        +     b0 );
+
+    for (int r = 0; r < 3; ++r)
+    for (int c = 0; c < 3; ++c) {
+        if (!isfinitef_(dst->vals[r][c])) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static Matrix3x3 Matrix3x3_concat(const Matrix3x3* A, const Matrix3x3* B) {
+    Matrix3x3 m = { { { 0,0,0 },{ 0,0,0 },{ 0,0,0 } } };
+    for (int r = 0; r < 3; r++)
+        for (int c = 0; c < 3; c++) {
+            m.vals[r][c] = A->vals[r][0] * B->vals[0][c]
+                         + A->vals[r][1] * B->vals[1][c]
+                         + A->vals[r][2] * B->vals[2][c];
+        }
+    return m;
+}
+
+static void float_XYZD50_to_grid16_lab(const float* xyz_float, uint8_t* grid16_lab) {
+    float v[3] = {
+            xyz_float[0] / kD50_x,
+            xyz_float[1] / kD50_y,
+            xyz_float[2] / kD50_z,
+    };
+    for (size_t i = 0; i < 3; ++i) {
+        v[i] = v[i] > 0.008856f ? cbrtf(v[i]) : v[i] * 7.787f + (16 / 116.0f);
+    }
+    const float L = v[1] * 116.0f - 16.0f;
+    const float a = (v[0] - v[1]) * 500.0f;
+    const float b = (v[1] - v[2]) * 200.0f;
+    const float Lab_unorm[3] = {
+            L * (1 / 100.f),
+            (a + 128.0f) * (1 / 255.0f),
+            (b + 128.0f) * (1 / 255.0f),
+    };
+    // This will encode L=1 as 0xFFFF. This matches how skcms will interpret the
+    // table, but the spec appears to indicate that the value should be 0xFF00.
+    // https://crbug.com/skia/13807
+    for (size_t i = 0; i < 3; ++i) {
+        reinterpret_cast<uint16_t*>(grid16_lab)[i] =
+                Endian_SwapBE16(float_round_to_unorm16(Lab_unorm[i]));
+    }
+}
+
+std::string IccHelper::get_desc_string(const jpegr_transfer_function tf,
+                                       const jpegr_color_gamut gamut) {
+    std::string result;
+    switch (gamut) {
+        case JPEGR_COLORGAMUT_BT709:
+            result += "sRGB";
+            break;
+        case JPEGR_COLORGAMUT_P3:
+            result += "Display P3";
+            break;
+        case JPEGR_COLORGAMUT_BT2100:
+            result += "Rec2020";
+            break;
+        default:
+            result += "Unknown";
+            break;
+    }
+    result += " Gamut with ";
+    switch (tf) {
+        case JPEGR_TF_SRGB:
+            result += "sRGB";
+            break;
+        case JPEGR_TF_LINEAR:
+            result += "Linear";
+            break;
+        case JPEGR_TF_PQ:
+            result += "PQ";
+            break;
+        case JPEGR_TF_HLG:
+            result += "HLG";
+            break;
+        default:
+            result += "Unknown";
+            break;
+    }
+    result += " Transfer";
+    return result;
+}
+
+sp<DataStruct> IccHelper::write_text_tag(const char* text) {
+    uint32_t text_length = strlen(text);
+    uint32_t header[] = {
+            Endian_SwapBE32(kTAG_TextType),                         // Type signature
+            0,                                                      // Reserved
+            Endian_SwapBE32(1),                                     // Number of records
+            Endian_SwapBE32(12),                                    // Record size (must be 12)
+            Endian_SwapBE32(SetFourByteTag('e', 'n', 'U', 'S')),    // English USA
+            Endian_SwapBE32(2 * text_length),                       // Length of string in bytes
+            Endian_SwapBE32(28),                                    // Offset of string
+    };
+
+    uint32_t total_length = text_length * 2 + sizeof(header);
+    total_length = (((total_length + 2) >> 2) << 2);  // 4 aligned
+    sp<DataStruct> dataStruct = new DataStruct(total_length);
+
+    if (!dataStruct->write(header, sizeof(header))) {
+        ALOGE("write_text_tag(): error in writing data");
+        return dataStruct;
+    }
+
+    for (size_t i = 0; i < text_length; i++) {
+        // Convert ASCII to big-endian UTF-16.
+        dataStruct->write8(0);
+        dataStruct->write8(text[i]);
+    }
+
+    return dataStruct;
+}
+
+sp<DataStruct> IccHelper::write_xyz_tag(float x, float y, float z) {
+    uint32_t data[] = {
+            Endian_SwapBE32(kXYZ_PCSSpace),
+            0,
+            static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(x))),
+            static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(y))),
+            static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(z))),
+    };
+    sp<DataStruct> dataStruct = new DataStruct(sizeof(data));
+    dataStruct->write(&data, sizeof(data));
+    return dataStruct;
+}
+
+sp<DataStruct> IccHelper::write_trc_tag(const int table_entries, const void* table_16) {
+    int total_length = 4 + 4 + 4 + table_entries * 2;
+    total_length = (((total_length + 2) >> 2) << 2);  // 4 aligned
+    sp<DataStruct> dataStruct = new DataStruct(total_length);
+    dataStruct->write32(Endian_SwapBE32(kTAG_CurveType));     // Type
+    dataStruct->write32(0);                                     // Reserved
+    dataStruct->write32(Endian_SwapBE32(table_entries));  // Value count
+    for (size_t i = 0; i < table_entries; ++i) {
+        uint16_t value = reinterpret_cast<const uint16_t*>(table_16)[i];
+        dataStruct->write16(value);
+    }
+    return dataStruct;
+}
+
+sp<DataStruct> IccHelper::write_trc_tag_for_linear() {
+    int total_length = 16;
+    sp<DataStruct> dataStruct = new DataStruct(total_length);
+    dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType));  // Type
+    dataStruct->write32(0);                                      // Reserved
+    dataStruct->write32(Endian_SwapBE16(kExponential_ParaCurveType));
+    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(1.0)));
+
+    return dataStruct;
+}
+
+float IccHelper::compute_tone_map_gain(const jpegr_transfer_function tf, float L) {
+    if (L <= 0.f) {
+        return 1.f;
+    }
+    if (tf == JPEGR_TF_PQ) {
+        // The PQ transfer function will map to the range [0, 1]. Linearly scale
+        // it up to the range [0, 10,000/203]. We will then tone map that back
+        // down to [0, 1].
+        constexpr float kInputMaxLuminance = 10000 / 203.f;
+        constexpr float kOutputMaxLuminance = 1.0;
+        L *= kInputMaxLuminance;
+
+        // Compute the tone map gain which will tone map from 10,000/203 to 1.0.
+        constexpr float kToneMapA = kOutputMaxLuminance / (kInputMaxLuminance * kInputMaxLuminance);
+        constexpr float kToneMapB = 1.f / kOutputMaxLuminance;
+        return kInputMaxLuminance * (1.f + kToneMapA * L) / (1.f + kToneMapB * L);
+    }
+    if (tf == JPEGR_TF_HLG) {
+        // Let Lw be the brightness of the display in nits.
+        constexpr float Lw = 203.f;
+        const float gamma = 1.2f + 0.42f * std::log(Lw / 1000.f) / std::log(10.f);
+        return std::pow(L, gamma - 1.f);
+    }
+    return 1.f;
+}
+
+sp<DataStruct> IccHelper::write_cicp_tag(uint32_t color_primaries,
+                                         uint32_t transfer_characteristics) {
+    int total_length = 12;  // 4 + 4 + 1 + 1 + 1 + 1
+    sp<DataStruct> dataStruct = new DataStruct(total_length);
+    dataStruct->write32(Endian_SwapBE32(kTAG_cicp));    // Type signature
+    dataStruct->write32(0);                             // Reserved
+    dataStruct->write8(color_primaries);                // Color primaries
+    dataStruct->write8(transfer_characteristics);       // Transfer characteristics
+    dataStruct->write8(0);                              // RGB matrix
+    dataStruct->write8(1);                              // Full range
+    return dataStruct;
+}
+
+void IccHelper::compute_lut_entry(const Matrix3x3& src_to_XYZD50, float rgb[3]) {
+    // Compute the matrices to convert from source to Rec2020, and from Rec2020 to XYZD50.
+    Matrix3x3 src_to_rec2020;
+    const Matrix3x3 rec2020_to_XYZD50 = kRec2020;
+    {
+        Matrix3x3 XYZD50_to_rec2020;
+        Matrix3x3_invert(&rec2020_to_XYZD50, &XYZD50_to_rec2020);
+        src_to_rec2020 = Matrix3x3_concat(&XYZD50_to_rec2020, &src_to_XYZD50);
+    }
+
+    // Convert the source signal to linear.
+    for (size_t i = 0; i < kNumChannels; ++i) {
+        rgb[i] = pqOetf(rgb[i]);
+    }
+
+    // Convert source gamut to Rec2020.
+    Matrix3x3_apply(&src_to_rec2020, rgb);
+
+    // Compute the luminance of the signal.
+    float L = bt2100Luminance({{{rgb[0], rgb[1], rgb[2]}}});
+
+    // Compute the tone map gain based on the luminance.
+    float tone_map_gain = compute_tone_map_gain(JPEGR_TF_PQ, L);
+
+    // Apply the tone map gain.
+    for (size_t i = 0; i < kNumChannels; ++i) {
+        rgb[i] *= tone_map_gain;
+    }
+
+    // Convert from Rec2020-linear to XYZD50.
+    Matrix3x3_apply(&rec2020_to_XYZD50, rgb);
+}
+
+sp<DataStruct> IccHelper::write_clut(const uint8_t* grid_points, const uint8_t* grid_16) {
+    uint32_t value_count = kNumChannels;
+    for (uint32_t i = 0; i < kNumChannels; ++i) {
+        value_count *= grid_points[i];
+    }
+
+    int total_length = 20 + 2 * value_count;
+    total_length = (((total_length + 2) >> 2) << 2);  // 4 aligned
+    sp<DataStruct> dataStruct = new DataStruct(total_length);
+
+    for (size_t i = 0; i < 16; ++i) {
+        dataStruct->write8(i < kNumChannels ? grid_points[i] : 0);  // Grid size
+    }
+    dataStruct->write8(2);  // Grid byte width (always 16-bit)
+    dataStruct->write8(0);  // Reserved
+    dataStruct->write8(0);  // Reserved
+    dataStruct->write8(0);  // Reserved
+
+    for (uint32_t i = 0; i < value_count; ++i) {
+        uint16_t value = reinterpret_cast<const uint16_t*>(grid_16)[i];
+        dataStruct->write16(value);
+    }
+
+    return dataStruct;
+}
+
+sp<DataStruct> IccHelper::write_mAB_or_mBA_tag(uint32_t type,
+                                               bool has_a_curves,
+                                               const uint8_t* grid_points,
+                                               const uint8_t* grid_16) {
+    const size_t b_curves_offset = 32;
+    sp<DataStruct> b_curves_data[kNumChannels];
+    sp<DataStruct> a_curves_data[kNumChannels];
+    size_t clut_offset = 0;
+    sp<DataStruct> clut;
+    size_t a_curves_offset = 0;
+
+    // The "B" curve is required.
+    for (size_t i = 0; i < kNumChannels; ++i) {
+        b_curves_data[i] = write_trc_tag_for_linear();
+    }
+
+    // The "A" curve and CLUT are optional.
+    if (has_a_curves) {
+        clut_offset = b_curves_offset;
+        for (size_t i = 0; i < kNumChannels; ++i) {
+            clut_offset += b_curves_data[i]->getLength();
+        }
+        clut = write_clut(grid_points, grid_16);
+
+        a_curves_offset = clut_offset + clut->getLength();
+        for (size_t i = 0; i < kNumChannels; ++i) {
+            a_curves_data[i] = write_trc_tag_for_linear();
+        }
+    }
+
+    int total_length = b_curves_offset;
+    for (size_t i = 0; i < kNumChannels; ++i) {
+        total_length += b_curves_data[i]->getLength();
+    }
+    if (has_a_curves) {
+        total_length += clut->getLength();
+        for (size_t i = 0; i < kNumChannels; ++i) {
+            total_length += a_curves_data[i]->getLength();
+        }
+    }
+    sp<DataStruct> dataStruct = new DataStruct(total_length);
+    dataStruct->write32(Endian_SwapBE32(type));             // Type signature
+    dataStruct->write32(0);                                 // Reserved
+    dataStruct->write8(kNumChannels);                       // Input channels
+    dataStruct->write8(kNumChannels);                       // Output channels
+    dataStruct->write16(0);                                 // Reserved
+    dataStruct->write32(Endian_SwapBE32(b_curves_offset));  // B curve offset
+    dataStruct->write32(Endian_SwapBE32(0));                // Matrix offset (ignored)
+    dataStruct->write32(Endian_SwapBE32(0));                // M curve offset (ignored)
+    dataStruct->write32(Endian_SwapBE32(clut_offset));      // CLUT offset
+    dataStruct->write32(Endian_SwapBE32(a_curves_offset));  // A curve offset
+    for (size_t i = 0; i < kNumChannels; ++i) {
+        if (dataStruct->write(b_curves_data[i]->getData(), b_curves_data[i]->getLength())) {
+            return dataStruct;
+        }
+    }
+    if (has_a_curves) {
+        dataStruct->write(clut->getData(), clut->getLength());
+        for (size_t i = 0; i < kNumChannels; ++i) {
+            dataStruct->write(a_curves_data[i]->getData(), a_curves_data[i]->getLength());
+        }
+    }
+    return dataStruct;
+}
+
+sp<DataStruct> IccHelper::writeIccProfile(jpegr_transfer_function tf, jpegr_color_gamut gamut) {
+    ICCHeader header;
+
+    std::vector<std::pair<uint32_t, sp<DataStruct>>> tags;
+
+    // Compute profile description tag
+    std::string desc = get_desc_string(tf, gamut);
+
+    tags.emplace_back(kTAG_desc, write_text_tag(desc.c_str()));
+
+    Matrix3x3 toXYZD50;
+    switch (gamut) {
+        case JPEGR_COLORGAMUT_BT709:
+            toXYZD50 = kSRGB;
+            break;
+        case JPEGR_COLORGAMUT_P3:
+            toXYZD50 = kDisplayP3;
+            break;
+        case JPEGR_COLORGAMUT_BT2100:
+            toXYZD50 = kRec2020;
+            break;
+        default:
+            // Should not fall here.
+            return new DataStruct(0);
+    }
+
+    // Compute primaries.
+    {
+        tags.emplace_back(kTAG_rXYZ,
+                write_xyz_tag(toXYZD50.vals[0][0], toXYZD50.vals[1][0], toXYZD50.vals[2][0]));
+        tags.emplace_back(kTAG_gXYZ,
+                write_xyz_tag(toXYZD50.vals[0][1], toXYZD50.vals[1][1], toXYZD50.vals[2][1]));
+        tags.emplace_back(kTAG_bXYZ,
+                write_xyz_tag(toXYZD50.vals[0][2], toXYZD50.vals[1][2], toXYZD50.vals[2][2]));
+    }
+
+    // Compute white point tag (must be D50)
+    tags.emplace_back(kTAG_wtpt, write_xyz_tag(kD50_x, kD50_y, kD50_z));
+
+    // Compute transfer curves.
+    if (tf != JPEGR_TF_PQ) {
+        if (tf == JPEGR_TF_HLG) {
+            std::vector<uint8_t> trc_table;
+            trc_table.resize(kTrcTableSize * 2);
+            for (uint32_t i = 0; i < kTrcTableSize; ++i) {
+                float x = i / (kTrcTableSize - 1.f);
+                float y = hlgOetf(x);
+                y *= compute_tone_map_gain(tf, y);
+                float_to_table16(y, &trc_table[2 * i]);
+            }
+
+            tags.emplace_back(kTAG_rTRC,
+                    write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data())));
+            tags.emplace_back(kTAG_gTRC,
+                    write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data())));
+            tags.emplace_back(kTAG_bTRC,
+                    write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data())));
+        } else {
+            tags.emplace_back(kTAG_rTRC, write_trc_tag_for_linear());
+            tags.emplace_back(kTAG_gTRC, write_trc_tag_for_linear());
+            tags.emplace_back(kTAG_bTRC, write_trc_tag_for_linear());
+        }
+    }
+
+    // Compute CICP.
+    if (tf == JPEGR_TF_HLG || tf == JPEGR_TF_PQ) {
+        // The CICP tag is present in ICC 4.4, so update the header's version.
+        header.version = Endian_SwapBE32(0x04400000);
+
+        uint32_t color_primaries = 0;
+        if (gamut == JPEGR_COLORGAMUT_BT709) {
+            color_primaries = kCICPPrimariesSRGB;
+        } else if (gamut == JPEGR_COLORGAMUT_P3) {
+            color_primaries = kCICPPrimariesP3;
+        }
+
+        uint32_t transfer_characteristics = 0;
+        if (tf == JPEGR_TF_SRGB) {
+            transfer_characteristics = kCICPTrfnSRGB;
+        } else if (tf == JPEGR_TF_LINEAR) {
+            transfer_characteristics = kCICPTrfnLinear;
+        } else if (tf == JPEGR_TF_PQ) {
+            transfer_characteristics = kCICPTrfnPQ;
+        } else if (tf == JPEGR_TF_HLG) {
+            transfer_characteristics = kCICPTrfnHLG;
+        }
+        tags.emplace_back(kTAG_cicp, write_cicp_tag(color_primaries, transfer_characteristics));
+    }
+
+    // Compute A2B0.
+    if (tf == JPEGR_TF_PQ) {
+        std::vector<uint8_t> a2b_grid;
+        a2b_grid.resize(kGridSize * kGridSize * kGridSize * kNumChannels * 2);
+        size_t a2b_grid_index = 0;
+        for (uint32_t r_index = 0; r_index < kGridSize; ++r_index) {
+            for (uint32_t g_index = 0; g_index < kGridSize; ++g_index) {
+                for (uint32_t b_index = 0; b_index < kGridSize; ++b_index) {
+                    float rgb[3] = {
+                            r_index / (kGridSize - 1.f),
+                            g_index / (kGridSize - 1.f),
+                            b_index / (kGridSize - 1.f),
+                    };
+                    compute_lut_entry(toXYZD50, rgb);
+                    float_XYZD50_to_grid16_lab(rgb, &a2b_grid[a2b_grid_index]);
+                    a2b_grid_index += 6;
+                }
+            }
+        }
+        const uint8_t* grid_16 = reinterpret_cast<const uint8_t*>(a2b_grid.data());
+
+        uint8_t grid_points[kNumChannels];
+        for (size_t i = 0; i < kNumChannels; ++i) {
+            grid_points[i] = kGridSize;
+        }
+
+        auto a2b_data = write_mAB_or_mBA_tag(kTAG_mABType,
+                                             /* has_a_curves */ true,
+                                             grid_points,
+                                             grid_16);
+        tags.emplace_back(kTAG_A2B0, std::move(a2b_data));
+    }
+
+    // Compute B2A0.
+    if (tf == JPEGR_TF_PQ) {
+        auto b2a_data = write_mAB_or_mBA_tag(kTAG_mBAType,
+                                             /* has_a_curves */ false,
+                                             /* grid_points */ nullptr,
+                                             /* grid_16 */ nullptr);
+        tags.emplace_back(kTAG_B2A0, std::move(b2a_data));
+    }
+
+    // Compute copyright tag
+    tags.emplace_back(kTAG_cprt, write_text_tag("Google Inc. 2022"));
+
+    // Compute the size of the profile.
+    size_t tag_data_size = 0;
+    for (const auto& tag : tags) {
+        tag_data_size += tag.second->getLength();
+    }
+    size_t tag_table_size = kICCTagTableEntrySize * tags.size();
+    size_t profile_size = kICCHeaderSize + tag_table_size + tag_data_size;
+
+    // Write the header.
+    header.data_color_space = Endian_SwapBE32(Signature_RGB);
+    header.pcs = Endian_SwapBE32(tf == JPEGR_TF_PQ ? Signature_Lab : Signature_XYZ);
+    header.size = Endian_SwapBE32(profile_size);
+    header.tag_count = Endian_SwapBE32(tags.size());
+
+    sp<DataStruct> dataStruct = new DataStruct(profile_size);
+    if (!dataStruct->write(&header, sizeof(header))) {
+        ALOGE("writeIccProfile(): error in header");
+        return dataStruct;
+    }
+
+    // Write the tag table. Track the offset and size of the previous tag to
+    // compute each tag's offset. An empty SkData indicates that the previous
+    // tag is to be reused.
+    uint32_t last_tag_offset = sizeof(header) + tag_table_size;
+    uint32_t last_tag_size = 0;
+    for (const auto& tag : tags) {
+        last_tag_offset = last_tag_offset + last_tag_size;
+        last_tag_size = tag.second->getLength();
+        uint32_t tag_table_entry[3] = {
+                Endian_SwapBE32(tag.first),
+                Endian_SwapBE32(last_tag_offset),
+                Endian_SwapBE32(last_tag_size),
+        };
+        if (!dataStruct->write(tag_table_entry, sizeof(tag_table_entry))) {
+            ALOGE("writeIccProfile(): error in writing tag table");
+            return dataStruct;
+        }
+    }
+
+    // Write the tags.
+    for (const auto& tag : tags) {
+        if (!dataStruct->write(tag.second->getData(), tag.second->getLength())) {
+            ALOGE("writeIccProfile(): error in writing tags");
+            return dataStruct;
+        }
+    }
+
+    return dataStruct;
+}
+
+} // namespace android::jpegrecoverymap
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/icc.h b/libs/jpegrecoverymap/include/jpegrecoverymap/icc.h
new file mode 100644
index 0000000..a81aa62
--- /dev/null
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/icc.h
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_JPEGRECOVERYMAP_ICC_H
+#define ANDROID_JPEGRECOVERYMAP_ICC_H
+
+#include <jpegrecoverymap/jpegr.h>
+#include <jpegrecoverymap/jpegrutils.h>
+#include <utils/RefBase.h>
+#include <cmath>
+#include <string>
+
+#ifdef USE_BIG_ENDIAN
+#undef USE_BIG_ENDIAN
+#define USE_BIG_ENDIAN true
+#endif
+
+namespace android::jpegrecoverymap {
+
+typedef int32_t              Fixed;
+#define Fixed1               (1 << 16)
+#define MaxS32FitsInFloat    2147483520
+#define MinS32FitsInFloat    (-MaxS32FitsInFloat)
+#define FixedToFloat(x)      ((x) * 1.52587890625e-5f)
+
+typedef struct Matrix3x3 {
+    float vals[3][3];
+} Matrix3x3;
+
+// The D50 illuminant.
+constexpr float kD50_x = 0.9642f;
+constexpr float kD50_y = 1.0000f;
+constexpr float kD50_z = 0.8249f;
+
+enum {
+    // data_color_space
+    Signature_CMYK = 0x434D594B,
+    Signature_Gray = 0x47524159,
+    Signature_RGB  = 0x52474220,
+
+    // pcs
+    Signature_Lab  = 0x4C616220,
+    Signature_XYZ  = 0x58595A20,
+};
+
+
+typedef uint32_t FourByteTag;
+static inline constexpr FourByteTag SetFourByteTag(char a, char b, char c, char d) {
+    return (((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)c << 8) | (uint32_t)d);
+}
+
+// This is equal to the header size according to the ICC specification (128)
+// plus the size of the tag count (4).  We include the tag count since we
+// always require it to be present anyway.
+static constexpr size_t kICCHeaderSize = 132;
+
+// Contains a signature (4), offset (4), and size (4).
+static constexpr size_t kICCTagTableEntrySize = 12;
+
+static constexpr uint32_t kDisplay_Profile    = SetFourByteTag('m', 'n', 't', 'r');
+static constexpr uint32_t kRGB_ColorSpace     = SetFourByteTag('R', 'G', 'B', ' ');
+static constexpr uint32_t kXYZ_PCSSpace       = SetFourByteTag('X', 'Y', 'Z', ' ');
+static constexpr uint32_t kACSP_Signature     = SetFourByteTag('a', 'c', 's', 'p');
+
+static constexpr uint32_t kTAG_desc           = SetFourByteTag('d', 'e', 's', 'c');
+static constexpr uint32_t kTAG_TextType       = SetFourByteTag('m', 'l', 'u', 'c');
+static constexpr uint32_t kTAG_rXYZ           = SetFourByteTag('r', 'X', 'Y', 'Z');
+static constexpr uint32_t kTAG_gXYZ           = SetFourByteTag('g', 'X', 'Y', 'Z');
+static constexpr uint32_t kTAG_bXYZ           = SetFourByteTag('b', 'X', 'Y', 'Z');
+static constexpr uint32_t kTAG_wtpt           = SetFourByteTag('w', 't', 'p', 't');
+static constexpr uint32_t kTAG_rTRC           = SetFourByteTag('r', 'T', 'R', 'C');
+static constexpr uint32_t kTAG_gTRC           = SetFourByteTag('g', 'T', 'R', 'C');
+static constexpr uint32_t kTAG_bTRC           = SetFourByteTag('b', 'T', 'R', 'C');
+static constexpr uint32_t kTAG_cicp           = SetFourByteTag('c', 'i', 'c', 'p');
+static constexpr uint32_t kTAG_cprt           = SetFourByteTag('c', 'p', 'r', 't');
+static constexpr uint32_t kTAG_A2B0           = SetFourByteTag('A', '2', 'B', '0');
+static constexpr uint32_t kTAG_B2A0           = SetFourByteTag('B', '2', 'A', '0');
+
+static constexpr uint32_t kTAG_CurveType      = SetFourByteTag('c', 'u', 'r', 'v');
+static constexpr uint32_t kTAG_mABType        = SetFourByteTag('m', 'A', 'B', ' ');
+static constexpr uint32_t kTAG_mBAType        = SetFourByteTag('m', 'B', 'A', ' ');
+static constexpr uint32_t kTAG_ParaCurveType  = SetFourByteTag('p', 'a', 'r', 'a');
+
+
+static constexpr Matrix3x3 kSRGB = {{
+    // ICC fixed-point (16.16) representation, taken from skcms. Please keep them exactly in sync.
+    // 0.436065674f, 0.385147095f, 0.143066406f,
+    // 0.222488403f, 0.716873169f, 0.060607910f,
+    // 0.013916016f, 0.097076416f, 0.714096069f,
+    { FixedToFloat(0x6FA2), FixedToFloat(0x6299), FixedToFloat(0x24A0) },
+    { FixedToFloat(0x38F5), FixedToFloat(0xB785), FixedToFloat(0x0F84) },
+    { FixedToFloat(0x0390), FixedToFloat(0x18DA), FixedToFloat(0xB6CF) },
+}};
+
+static constexpr Matrix3x3 kDisplayP3 = {{
+    {  0.515102f,   0.291965f,  0.157153f  },
+    {  0.241182f,   0.692236f,  0.0665819f },
+    { -0.00104941f, 0.0418818f, 0.784378f  },
+}};
+
+static constexpr Matrix3x3 kRec2020 = {{
+    {  0.673459f,   0.165661f,  0.125100f  },
+    {  0.279033f,   0.675338f,  0.0456288f },
+    { -0.00193139f, 0.0299794f, 0.797162f  },
+}};
+
+static constexpr uint32_t kCICPPrimariesSRGB = 1;
+static constexpr uint32_t kCICPPrimariesP3 = 12;
+static constexpr uint32_t kCICPPrimariesRec2020 = 9;
+
+static constexpr uint32_t kCICPTrfnSRGB = 1;
+static constexpr uint32_t kCICPTrfnLinear = 8;
+static constexpr uint32_t kCICPTrfnPQ = 16;
+static constexpr uint32_t kCICPTrfnHLG = 18;
+
+enum ParaCurveType {
+    kExponential_ParaCurveType = 0,
+    kGAB_ParaCurveType         = 1,
+    kGABC_ParaCurveType        = 2,
+    kGABDE_ParaCurveType       = 3,
+    kGABCDEF_ParaCurveType     = 4,
+};
+
+/**
+ *  Return the closest int for the given float. Returns MaxS32FitsInFloat for NaN.
+ */
+static inline int float_saturate2int(float x) {
+    x = x < MaxS32FitsInFloat ? x : MaxS32FitsInFloat;
+    x = x > MinS32FitsInFloat ? x : MinS32FitsInFloat;
+    return (int)x;
+}
+
+static Fixed float_round_to_fixed(float x) {
+    return float_saturate2int((float)floor((double)x * Fixed1 + 0.5));
+}
+
+static uint16_t float_round_to_unorm16(float x) {
+    x = x * 65535.f + 0.5;
+    if (x > 65535) return 65535;
+    if (x < 0) return 0;
+    return static_cast<uint16_t>(x);
+}
+
+static void float_to_table16(const float f, uint8_t* table_16) {
+    *reinterpret_cast<uint16_t*>(table_16) = Endian_SwapBE16(float_round_to_unorm16(f));
+}
+
+static bool isfinitef_(float x) { return 0 == x*0; }
+
+struct ICCHeader {
+    // Size of the profile (computed)
+    uint32_t size;
+    // Preferred CMM type (ignored)
+    uint32_t cmm_type = 0;
+    // Version 4.3 or 4.4 if CICP is included.
+    uint32_t version = Endian_SwapBE32(0x04300000);
+    // Display device profile
+    uint32_t profile_class = Endian_SwapBE32(kDisplay_Profile);
+    // RGB input color space;
+    uint32_t data_color_space = Endian_SwapBE32(kRGB_ColorSpace);
+    // Profile connection space.
+    uint32_t pcs = Endian_SwapBE32(kXYZ_PCSSpace);
+    // Date and time (ignored)
+    uint8_t creation_date_time[12] = {0};
+    // Profile signature
+    uint32_t signature = Endian_SwapBE32(kACSP_Signature);
+    // Platform target (ignored)
+    uint32_t platform = 0;
+    // Flags: not embedded, can be used independently
+    uint32_t flags = 0x00000000;
+    // Device manufacturer (ignored)
+    uint32_t device_manufacturer = 0;
+    // Device model (ignored)
+    uint32_t device_model = 0;
+    // Device attributes (ignored)
+    uint8_t device_attributes[8] = {0};
+    // Relative colorimetric rendering intent
+    uint32_t rendering_intent = Endian_SwapBE32(1);
+    // D50 standard illuminant (X, Y, Z)
+    uint32_t illuminant_X = Endian_SwapBE32(float_round_to_fixed(kD50_x));
+    uint32_t illuminant_Y = Endian_SwapBE32(float_round_to_fixed(kD50_y));
+    uint32_t illuminant_Z = Endian_SwapBE32(float_round_to_fixed(kD50_z));
+    // Profile creator (ignored)
+    uint32_t creator = 0;
+    // Profile id checksum (ignored)
+    uint8_t profile_id[16] = {0};
+    // Reserved (ignored)
+    uint8_t reserved[28] = {0};
+    // Technically not part of header, but required
+    uint32_t tag_count = 0;
+};
+
+class IccHelper {
+private:
+    static constexpr uint32_t kTrcTableSize = 65;
+    static constexpr uint32_t kGridSize = 17;
+    static constexpr size_t kNumChannels = 3;
+
+    static sp<DataStruct> write_text_tag(const char* text);
+    static std::string get_desc_string(const jpegr_transfer_function tf,
+                                       const jpegr_color_gamut gamut);
+    static sp<DataStruct> write_xyz_tag(float x, float y, float z);
+    static sp<DataStruct> write_trc_tag(const int table_entries, const void* table_16);
+    static sp<DataStruct> write_trc_tag_for_linear();
+    static float compute_tone_map_gain(const jpegr_transfer_function tf, float L);
+    static sp<DataStruct> write_cicp_tag(uint32_t color_primaries,
+                                         uint32_t transfer_characteristics);
+    static sp<DataStruct> write_mAB_or_mBA_tag(uint32_t type,
+                                               bool has_a_curves,
+                                               const uint8_t* grid_points,
+                                               const uint8_t* grid_16);
+    static void compute_lut_entry(const Matrix3x3& src_to_XYZD50, float rgb[3]);
+    static sp<DataStruct> write_clut(const uint8_t* grid_points, const uint8_t* grid_16);
+
+public:
+    static sp<DataStruct> writeIccProfile(const jpegr_transfer_function tf,
+                                          const jpegr_color_gamut gamut);
+};
+}  // namespace android::jpegrecoverymap
+
+#endif //ANDROID_JPEGRECOVERYMAP_ICC_H
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h
index 5455ba6..e2023a6 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h
@@ -38,6 +38,14 @@
   JPEGR_TF_SRGB = 3,
 } jpegr_transfer_function;
 
+// Target output formats for decoder
+typedef enum {
+  JPEGR_OUTPUT_SDR,          // SDR in RGBA_8888 color format
+  JPEGR_OUTPUT_HDR_LINEAR,   // HDR in F16 color format (linear)
+  JPEGR_OUTPUT_HDR_PQ,       // HDR in RGBA_1010102 color format (PQ transfer function)
+  JPEGR_OUTPUT_HDR_HLG,      // HDR in RGBA_1010102 color format (HLG transfer function)
+} jpegr_output_format;
+
 struct jpegr_info_struct {
     size_t width;
     size_t height;
@@ -83,7 +91,10 @@
     int length;
 };
 
-struct jpegr_metadata {
+/*
+ * Holds information for recovery map related metadata.
+ */
+struct jpegr_metadata_struct {
   // JPEG/R version
   uint32_t version;
   // Max Content Boost for the map
@@ -95,12 +106,14 @@
 typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr;
 typedef struct jpegr_compressed_struct* jr_compressed_ptr;
 typedef struct jpegr_exif_struct* jr_exif_ptr;
-typedef struct jpegr_metadata* jr_metadata_ptr;
+typedef struct jpegr_metadata_struct* jr_metadata_ptr;
 typedef struct jpegr_info_struct* jr_info_ptr;
 
 class JpegR {
 public:
     /*
+     * Experimental only
+     *
      * Encode API-0
      * Compress JPEGR image from 10-bit HDR YUV.
      *
@@ -191,24 +204,40 @@
      * Decode API
      * Decompress JPEGR image.
      *
-     * The output JPEGR image is in RGBA_1010102 data format if decoding to HDR.
-     * @param compressed_jpegr_image compressed JPEGR image
-     * @param dest destination of the uncompressed JPEGR image
-     * @param exif destination of the decoded EXIF metadata.
-     * @param request_sdr flag that request SDR output. If set to true, decoder will only decode
-     *                    the primary image which is SDR. Setting of request_sdr and input source
-     *                    (HDR or SDR) can be found in the table below:
-     *                    |  input source  |  request_sdr  |  output of decoding  |
-     *                    |       HDR      |     true      |          SDR         |
-     *                    |       HDR      |     false     |          HDR         |
-     *                    |       SDR      |     true      |          SDR         |
-     *                    |       SDR      |     false     |          SDR         |
+     * @param compressed_jpegr_image compressed JPEGR image.
+     * @param dest destination of the uncompressed JPEGR image.
+     * @param exif destination of the decoded EXIF metadata. The default value is NULL where the
+                   decoder will do nothing about it. If configured not NULL the decoder will write
+                   EXIF data into this structure. The format is defined in {@code jpegr_exif_struct}
+     * @param output_format flag for setting output color format. Its value configures the output
+                            color format. The default value is {@code JPEGR_OUTPUT_HDR_LINEAR}.
+                            ----------------------------------------------------------------------
+                            |   output_format          |    decoded color format to be written   |
+                            ----------------------------------------------------------------------
+                            |     JPEGR_OUTPUT_SDR     |                RGBA_8888                |
+                            ----------------------------------------------------------------------
+                            | JPEGR_OUTPUT_HDR_LINEAR  |        (default)RGBA_F16 linear         |
+                            ----------------------------------------------------------------------
+                            |   JPEGR_OUTPUT_HDR_PQ    |             RGBA_1010102 PQ             |
+                            ----------------------------------------------------------------------
+                            |   JPEGR_OUTPUT_HDR_HLG   |            RGBA_1010102 HLG             |
+                            ----------------------------------------------------------------------
+     * @param recovery_map destination of the decoded recovery map. The default value is NULL where
+                           the decoder will do nothing about it. If configured not NULL the decoder
+                           will write the decoded recovery_map data into this structure. The format
+                           is defined in {@code jpegr_uncompressed_struct}.
+     * @param metadata destination of the decoded metadata. The default value is NULL where the
+                       decoder will do nothing about it. If configured not NULL the decoder will
+                       write metadata into this structure. the format of metadata is defined in
+                       {@code jpegr_metadata}.
      * @return NO_ERROR if decoding succeeds, error code if error occurs.
      */
     status_t decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
                          jr_uncompressed_ptr dest,
                          jr_exif_ptr exif = nullptr,
-                         bool request_sdr = false);
+                         jpegr_output_format output_format = JPEGR_OUTPUT_HDR_LINEAR,
+                         jr_uncompressed_ptr recovery_map = nullptr,
+                         jr_metadata_ptr metadata = nullptr);
 
     /*
     * Gets Info from JPEGR file without decoding it.
@@ -249,12 +278,16 @@
      * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
      * @param uncompressed_recovery_map uncompressed recovery map
      * @param metadata JPEG/R metadata extracted from XMP.
+     * @param output_format flag for setting output color format. if set to
+     *                      {@code JPEGR_OUTPUT_SDR}, decoder will only decode the primary image
+     *                      which is SDR. Default value is JPEGR_OUTPUT_HDR_LINEAR.
      * @param dest reconstructed HDR image
      * @return NO_ERROR if calculation succeeds, error code if error occurs.
      */
     status_t applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
                               jr_uncompressed_ptr uncompressed_recovery_map,
                               jr_metadata_ptr metadata,
+                              jpegr_output_format output_format,
                               jr_uncompressed_ptr dest);
 
 private:
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h
index 4145853..dd06fa2 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h
@@ -27,7 +27,25 @@
 
 namespace android::jpegrecoverymap {
 
-struct jpegr_metadata;
+static constexpr uint32_t EndianSwap32(uint32_t value) {
+    return ((value & 0xFF) << 24) |
+           ((value & 0xFF00) << 8) |
+           ((value & 0xFF0000) >> 8) |
+           (value >> 24);
+}
+static inline uint16_t EndianSwap16(uint16_t value) {
+    return static_cast<uint16_t>((value >> 8) | ((value & 0xFF) << 8));
+}
+
+#if USE_BIG_ENDIAN
+    #define Endian_SwapBE32(n) EndianSwap32(n)
+    #define Endian_SwapBE16(n) EndianSwap16(n)
+#else
+    #define Endian_SwapBE32(n) (n)
+    #define Endian_SwapBE16(n) (n)
+#endif
+
+struct jpegr_metadata_struct;
 /*
  * Mutable data structure. Holds information for metadata.
  */
@@ -69,7 +87,7 @@
  * @param metadata place to store HDR metadata values
  * @return true if metadata is successfully retrieved, false otherwise
 */
-bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata);
+bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata_struct* metadata);
 
 /*
  * This method generates XMP metadata for the primary image.
@@ -138,7 +156,7 @@
  * @param metadata JPEG/R metadata to encode as XMP
  * @return XMP metadata in type of string
  */
- std::string generateXmpForSecondaryImage(jpegr_metadata& metadata);
+ std::string generateXmpForSecondaryImage(jpegr_metadata_struct& metadata);
 }  // namespace android::jpegrecoverymap
 
 #endif //ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h b/libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h
index 7dca916..cf3387d 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h
@@ -19,25 +19,13 @@
 
 #include <jpegrecoverymap/jpegrutils.h>
 
-namespace android::jpegrecoverymap {
-static constexpr uint32_t EndianSwap32(uint32_t value) {
-    return ((value & 0xFF) << 24) |
-           ((value & 0xFF00) << 8) |
-           ((value & 0xFF0000) >> 8) |
-           (value >> 24);
-}
-static inline uint16_t EndianSwap16(uint16_t value) {
-    return static_cast<uint16_t>((value >> 8) | ((value & 0xFF) << 8));
-}
+#ifdef USE_BIG_ENDIAN
+#undef USE_BIG_ENDIAN
 #define USE_BIG_ENDIAN true
-#if USE_BIG_ENDIAN
-    #define Endian_SwapBE32(n) EndianSwap32(n)
-    #define Endian_SwapBE16(n) EndianSwap16(n)
-#else
-    #define Endian_SwapBE32(n) (n)
-    #define Endian_SwapBE16(n) (n)
 #endif
 
+namespace android::jpegrecoverymap {
+
 constexpr size_t kNumPictures = 2;
 constexpr size_t kMpEndianSize = 4;
 constexpr uint16_t kTagSerializedCount = 3;
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
index c12cee9..8b5318f 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
@@ -115,6 +115,14 @@
   return temp /= rhs;
 }
 
+inline uint16_t floatToHalf(float f) {
+  uint32_t x = *((uint32_t*)&f);
+  uint16_t h = ((x >> 16) & 0x8000)
+             | ((((x & 0x7f800000) - 0x38000000) >> 13) & 0x7c00)
+             | ((x >> 13) & 0x03ff);
+  return h;
+}
+
 constexpr size_t kRecoveryFactorPrecision = 10;
 constexpr size_t kRecoveryFactorNumEntries = 1 << kRecoveryFactorPrecision;
 struct RecoveryLUT {
@@ -392,6 +400,13 @@
  */
 uint32_t colorToRgba1010102(Color e_gamma);
 
+/*
+ * Convert from Color to F16.
+ *
+ * Alpha always set to 1.0.
+ */
+uint64_t colorToRgbaF16(Color e_gamma);
+
 } // namespace android::jpegrecoverymap
 
 #endif // ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H
diff --git a/libs/jpegrecoverymap/jpegr.cpp b/libs/jpegrecoverymap/jpegr.cpp
index c22020a..e197bf0 100644
--- a/libs/jpegrecoverymap/jpegr.cpp
+++ b/libs/jpegrecoverymap/jpegr.cpp
@@ -20,6 +20,7 @@
 #include <jpegrecoverymap/recoverymapmath.h>
 #include <jpegrecoverymap/jpegrutils.h>
 #include <jpegrecoverymap/multipictureformat.h>
+#include <jpegrecoverymap/icc.h>
 
 #include <image_io/jpeg/jpeg_marker.h>
 #include <image_io/jpeg/jpeg_info.h>
@@ -27,10 +28,6 @@
 #include <image_io/jpeg/jpeg_info_builder.h>
 #include <image_io/base/data_segment_data_source.h>
 #include <utils/Log.h>
-#include "SkColorSpace.h"
-#include "SkData.h"
-#include "SkICC.h"
-#include "SkRefCnt.h"
 
 #include <map>
 #include <memory>
@@ -89,21 +86,6 @@
   return cpuCoreCount;
 }
 
-static const map<jpegrecoverymap::jpegr_color_gamut, skcms_Matrix3x3> jrGamut_to_skGamut {
-    {JPEGR_COLORGAMUT_BT709,     SkNamedGamut::kSRGB},
-    {JPEGR_COLORGAMUT_P3,        SkNamedGamut::kDisplayP3},
-    {JPEGR_COLORGAMUT_BT2100,    SkNamedGamut::kRec2020},
-};
-
-static const map<
-        jpegrecoverymap::jpegr_transfer_function,
-        skcms_TransferFunction> jrTransFunc_to_skTransFunc {
-    {JPEGR_TF_SRGB,        SkNamedTransferFn::kSRGB},
-    {JPEGR_TF_LINEAR,      SkNamedTransferFn::kLinear},
-    {JPEGR_TF_HLG,         SkNamedTransferFn::kHLG},
-    {JPEGR_TF_PQ,          SkNamedTransferFn::kPQ},
-};
-
 /* Encode API-0 */
 status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
                             jpegr_transfer_function hdr_tf,
@@ -125,7 +107,7 @@
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
 
-  jpegr_metadata metadata;
+  jpegr_metadata_struct metadata;
   metadata.version = kJpegrVersion;
 
   jpegr_uncompressed_struct uncompressed_yuv_420_image;
@@ -146,15 +128,14 @@
   compressed_map.data = compressed_map_data.get();
   JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
 
-  sk_sp<SkData> icc = SkWriteICCProfile(
-          jrTransFunc_to_skTransFunc.at(JPEGR_TF_SRGB),
-          jrGamut_to_skGamut.at(uncompressed_yuv_420_image.colorGamut));
+  sp<DataStruct> icc = IccHelper::writeIccProfile(JPEGR_TF_SRGB,
+                                                  uncompressed_yuv_420_image.colorGamut);
 
   JpegEncoderHelper jpeg_encoder;
   if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data,
                                   uncompressed_yuv_420_image.width,
                                   uncompressed_yuv_420_image.height, quality,
-                                  icc.get()->data(), icc.get()->size())) {
+                                  icc->getData(), icc->getLength())) {
     return ERROR_JPEGR_ENCODE_ERROR;
   }
   jpegr_compressed_struct jpeg;
@@ -195,7 +176,7 @@
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
 
-  jpegr_metadata metadata;
+  jpegr_metadata_struct metadata;
   metadata.version = kJpegrVersion;
 
   jpegr_uncompressed_struct map;
@@ -210,15 +191,14 @@
   compressed_map.data = compressed_map_data.get();
   JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
 
-  sk_sp<SkData> icc = SkWriteICCProfile(
-          jrTransFunc_to_skTransFunc.at(JPEGR_TF_SRGB),
-          jrGamut_to_skGamut.at(uncompressed_yuv_420_image->colorGamut));
+  sp<DataStruct> icc = IccHelper::writeIccProfile(JPEGR_TF_SRGB,
+                                                  uncompressed_yuv_420_image->colorGamut);
 
   JpegEncoderHelper jpeg_encoder;
   if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
                                   uncompressed_yuv_420_image->width,
                                   uncompressed_yuv_420_image->height, quality,
-                                  icc.get()->data(), icc.get()->size())) {
+                                  icc->getData(), icc->getLength())) {
     return ERROR_JPEGR_ENCODE_ERROR;
   }
   jpegr_compressed_struct jpeg;
@@ -255,7 +235,7 @@
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
 
-  jpegr_metadata metadata;
+  jpegr_metadata_struct metadata;
   metadata.version = kJpegrVersion;
 
   jpegr_uncompressed_struct map;
@@ -308,7 +288,7 @@
     return ERROR_JPEGR_RESOLUTION_MISMATCH;
   }
 
-  jpegr_metadata metadata;
+  jpegr_metadata_struct metadata;
   metadata.version = kJpegrVersion;
 
   jpegr_uncompressed_struct map;
@@ -351,14 +331,14 @@
 status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
                             jr_uncompressed_ptr dest,
                             jr_exif_ptr exif,
-                            bool request_sdr) {
+                            jpegr_output_format output_format,
+                            jr_uncompressed_ptr recovery_map,
+                            jr_metadata_ptr metadata) {
   if (compressed_jpegr_image == nullptr || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
-  // TODO: fill EXIF data
-  (void) exif;
 
-  if (request_sdr) {
+  if (output_format == JPEGR_OUTPUT_SDR) {
     JpegDecoderHelper jpeg_decoder;
     if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length,
                                       true)) {
@@ -372,21 +352,72 @@
            uncompressed_rgba_image.width * uncompressed_rgba_image.height * 4);
     dest->width = uncompressed_rgba_image.width;
     dest->height = uncompressed_rgba_image.height;
-    return NO_ERROR;
+
+    if (recovery_map == nullptr && exif == nullptr) {
+      return NO_ERROR;
+    }
+
+    if (exif != nullptr) {
+      if (exif->data == nullptr) {
+        return ERROR_JPEGR_INVALID_NULL_PTR;
+      }
+      if (exif->length < jpeg_decoder.getEXIFSize()) {
+        return ERROR_JPEGR_BUFFER_TOO_SMALL;
+      }
+      memcpy(exif->data, jpeg_decoder.getEXIFPtr(), jpeg_decoder.getEXIFSize());
+      exif->length = jpeg_decoder.getEXIFSize();
+    }
+    if (recovery_map == nullptr) {
+      return NO_ERROR;
+    }
   }
 
   jpegr_compressed_struct compressed_map;
-  jpegr_metadata metadata;
   JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
 
+  JpegDecoderHelper recovery_map_decoder;
+  if (!recovery_map_decoder.decompressImage(compressed_map.data, compressed_map.length)) {
+    return ERROR_JPEGR_DECODE_ERROR;
+  }
+
+  if (recovery_map != nullptr) {
+    recovery_map->width = recovery_map_decoder.getDecompressedImageWidth();
+    recovery_map->height = recovery_map_decoder.getDecompressedImageHeight();
+    int size = recovery_map->width * recovery_map->height;
+    recovery_map->data = malloc(size);
+    memcpy(recovery_map->data, recovery_map_decoder.getDecompressedImagePtr(), size);
+  }
+
+  jpegr_metadata_struct jr_metadata;
+  if (!getMetadataFromXMP(static_cast<uint8_t*>(recovery_map_decoder.getXMPPtr()),
+                          recovery_map_decoder.getXMPSize(), &jr_metadata)) {
+    return ERROR_JPEGR_DECODE_ERROR;
+  }
+
+  if (metadata != nullptr) {
+      metadata->version = jr_metadata.version;
+      metadata->minContentBoost = jr_metadata.minContentBoost;
+      metadata->maxContentBoost = jr_metadata.maxContentBoost;
+  }
+
+  if (output_format == JPEGR_OUTPUT_SDR) {
+    return NO_ERROR;
+  }
+
   JpegDecoderHelper jpeg_decoder;
   if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) {
     return ERROR_JPEGR_DECODE_ERROR;
   }
 
-  JpegDecoderHelper recovery_map_decoder;
-  if (!recovery_map_decoder.decompressImage(compressed_map.data, compressed_map.length)) {
-    return ERROR_JPEGR_DECODE_ERROR;
+  if (exif != nullptr) {
+    if (exif->data == nullptr) {
+      return ERROR_JPEGR_INVALID_NULL_PTR;
+    }
+    if (exif->length < jpeg_decoder.getEXIFSize()) {
+      return ERROR_JPEGR_BUFFER_TOO_SMALL;
+    }
+    memcpy(exif->data, jpeg_decoder.getEXIFPtr(), jpeg_decoder.getEXIFSize());
+    exif->length = jpeg_decoder.getEXIFSize();
   }
 
   jpegr_uncompressed_struct map;
@@ -399,12 +430,8 @@
   uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
   uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
 
-  if (!getMetadataFromXMP(static_cast<uint8_t*>(recovery_map_decoder.getXMPPtr()),
-                          recovery_map_decoder.getXMPSize(), &metadata)) {
-    return ERROR_JPEGR_DECODE_ERROR;
-  }
-
-  JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest));
+  JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &jr_metadata, output_format,
+                               dest));
   return NO_ERROR;
 }
 
@@ -639,6 +666,7 @@
 status_t JpegR::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
                                  jr_uncompressed_ptr uncompressed_recovery_map,
                                  jr_metadata_ptr metadata,
+                                 jpegr_output_format output_format,
                                  jr_uncompressed_ptr dest) {
   if (uncompressed_yuv_420_image == nullptr
    || uncompressed_recovery_map == nullptr
@@ -654,18 +682,12 @@
 
   JobQueue jobQueue;
   std::function<void()> applyRecMap = [uncompressed_yuv_420_image, uncompressed_recovery_map,
-                                       metadata, dest, &jobQueue, &idwTable,
+                                       metadata, dest, &jobQueue, &idwTable, output_format,
                                        &recoveryLUT]() -> void {
     const float hdr_ratio = metadata->maxContentBoost;
     size_t width = uncompressed_yuv_420_image->width;
     size_t height = uncompressed_yuv_420_image->height;
 
-#if USE_HLG_OETF_LUT
-    ColorTransformFn hdrOetf = hlgOetfLUT;
-#else
-    ColorTransformFn hdrOetf = hlgOetf;
-#endif
-
     size_t rowStart, rowEnd;
     while (jobQueue.dequeueJob(rowStart, rowEnd)) {
       for (size_t y = rowStart; y < rowEnd; ++y) {
@@ -693,11 +715,44 @@
 #else
           Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata);
 #endif
-          Color rgb_gamma_hdr = hdrOetf(rgb_hdr / metadata->maxContentBoost);
-          uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
-
+          rgb_hdr = rgb_hdr / metadata->maxContentBoost;
           size_t pixel_idx = x + y * width;
-          reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102;
+
+          switch (output_format) {
+            case JPEGR_OUTPUT_HDR_LINEAR:
+            {
+              uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr);
+              reinterpret_cast<uint64_t*>(dest->data)[pixel_idx] = rgba_f16;
+              break;
+            }
+            case JPEGR_OUTPUT_HDR_HLG:
+            {
+#if USE_HLG_OETF_LUT
+              ColorTransformFn hdrOetf = hlgOetfLUT;
+#else
+              ColorTransformFn hdrOetf = hlgOetf;
+#endif
+              Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
+              uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
+              reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
+              break;
+            }
+            case JPEGR_OUTPUT_HDR_PQ:
+            {
+#if USE_HLG_OETF_LUT
+              ColorTransformFn hdrOetf = pqOetfLUT;
+#else
+              ColorTransformFn hdrOetf = pqOetf;
+#endif
+              Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
+              uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
+              reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
+              break;
+            }
+            default:
+            {}
+              // Should be impossible to hit after input validation.
+          }
         }
       }
     }
diff --git a/libs/jpegrecoverymap/jpegrutils.cpp b/libs/jpegrecoverymap/jpegrutils.cpp
index 38b78ad..ff96447 100644
--- a/libs/jpegrecoverymap/jpegrutils.cpp
+++ b/libs/jpegrecoverymap/jpegrutils.cpp
@@ -253,7 +253,7 @@
 const string XMPXmlHandler::minContentBoostAttrName = kMapGainMapMin;
 const string XMPXmlHandler::maxContentBoostAttrName = kMapGainMapMax;
 
-bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) {
+bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata_struct* metadata) {
     string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
 
     if (xmp_size < nameSpace.size()+2) {
@@ -327,7 +327,7 @@
   return ss.str();
 }
 
-string generateXmpForSecondaryImage(jpegr_metadata& metadata) {
+string generateXmpForSecondaryImage(jpegr_metadata_struct& metadata) {
   const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
   const vector<string> kLiItem({string("rdf:li"), kConItem});
 
diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/recoverymapmath.cpp
index 7812e18..20c32ed 100644
--- a/libs/jpegrecoverymap/recoverymapmath.cpp
+++ b/libs/jpegrecoverymap/recoverymapmath.cpp
@@ -631,4 +631,11 @@
        | (0x3 << 30);  // Set alpha to 1.0
 }
 
+uint64_t colorToRgbaF16(Color e_gamma) {
+  return (uint64_t) floatToHalf(e_gamma.r)
+       | (((uint64_t) floatToHalf(e_gamma.g)) << 16)
+       | (((uint64_t) floatToHalf(e_gamma.b)) << 32)
+       | (((uint64_t) floatToHalf(1.0f)) << 48);
+}
+
 } // namespace android::jpegrecoverymap
diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp
index 61b3db9..d5da7fb 100644
--- a/libs/jpegrecoverymap/tests/Android.bp
+++ b/libs/jpegrecoverymap/tests/Android.bp
@@ -39,7 +39,6 @@
         "libjpegdecoder",
         "libjpegencoder",
         "libjpegrecoverymap",
-        "libskia",
         "libutils",
     ],
 }
diff --git a/libs/jpegrecoverymap/tests/jpegr_test.cpp b/libs/jpegrecoverymap/tests/jpegr_test.cpp
index df212e1..be4b972 100644
--- a/libs/jpegrecoverymap/tests/jpegr_test.cpp
+++ b/libs/jpegrecoverymap/tests/jpegr_test.cpp
@@ -152,7 +152,7 @@
 
   timerStart(&applyRecMapTime);
   for (auto i = 0; i < kProfileCount; i++) {
-      ASSERT_EQ(OK, applyRecoveryMap(yuv420Image, map, metadata, dest));
+      ASSERT_EQ(OK, applyRecoveryMap(yuv420Image, map, metadata, JPEGR_OUTPUT_HDR_HLG, dest));
   }
   timerStop(&applyRecMapTime);
 
@@ -170,11 +170,11 @@
   jpegRCodec.encodeJPEGR(nullptr, nullptr, nullptr, static_cast<jpegr_transfer_function>(0),
                          nullptr);
   jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast<jpegr_transfer_function>(0), nullptr);
-  jpegRCodec.decodeJPEGR(nullptr, nullptr, nullptr, false);
+  jpegRCodec.decodeJPEGR(nullptr, nullptr, nullptr);
 }
 
 TEST_F(JpegRTest, writeXmpThenRead) {
-  jpegr_metadata metadata_expected;
+  jpegr_metadata_struct metadata_expected;
   metadata_expected.maxContentBoost = 1.25;
   metadata_expected.minContentBoost = 0.75;
   const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
@@ -189,7 +189,7 @@
   xmpData.insert(xmpData.end(), reinterpret_cast<const uint8_t*>(xmp.c_str()),
                   reinterpret_cast<const uint8_t*>(xmp.c_str()) + xmp.size());
 
-  jpegr_metadata metadata_read;
+  jpegr_metadata_struct metadata_read;
   EXPECT_TRUE(getMetadataFromXMP(xmpData.data(), xmpData.size(), &metadata_read));
   ASSERT_EQ(metadata_expected.maxContentBoost, metadata_read.maxContentBoost);
   ASSERT_EQ(metadata_expected.minContentBoost, metadata_read.minContentBoost);
@@ -228,7 +228,7 @@
   }
 
   jpegr_uncompressed_struct decodedJpegR;
-  int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
+  int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8;
   decodedJpegR.data = malloc(decodedJpegRSize);
   ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR);
   if (ret != OK) {
@@ -236,7 +236,7 @@
   }
   if (SAVE_DECODING_RESULT) {
     // Output image data to file
-    std::string filePath = "/sdcard/Documents/decoded_from_p010_input.rgb10";
+    std::string filePath = "/sdcard/Documents/decoded_from_p010_input.rgb";
     std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
     if (!imageFile.is_open()) {
       ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
@@ -289,7 +289,7 @@
   }
 
   jpegr_uncompressed_struct decodedJpegR;
-  int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
+  int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8;
   decodedJpegR.data = malloc(decodedJpegRSize);
   ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR);
   if (ret != OK) {
@@ -297,7 +297,7 @@
   }
   if (SAVE_DECODING_RESULT) {
     // Output image data to file
-    std::string filePath = "/sdcard/Documents/decoded_from_p010_yuv420p_input.rgb10";
+    std::string filePath = "/sdcard/Documents/decoded_from_p010_yuv420p_input.rgb";
     std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
     if (!imageFile.is_open()) {
       ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
@@ -354,7 +354,7 @@
   }
 
   jpegr_uncompressed_struct decodedJpegR;
-  int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
+  int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8;
   decodedJpegR.data = malloc(decodedJpegRSize);
   ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR);
   if (ret != OK) {
@@ -362,7 +362,7 @@
   }
   if (SAVE_DECODING_RESULT) {
     // Output image data to file
-    std::string filePath = "/sdcard/Documents/decoded_from_p010_yuv420p_jpeg_input.rgb10";
+    std::string filePath = "/sdcard/Documents/decoded_from_p010_yuv420p_jpeg_input.rgb";
     std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
     if (!imageFile.is_open()) {
       ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
@@ -435,7 +435,7 @@
   }
 
   jpegr_uncompressed_struct decodedJpegR;
-  int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
+  int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8;
   decodedJpegR.data = malloc(decodedJpegRSize);
   ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR);
   if (ret != OK) {
@@ -443,7 +443,7 @@
   }
   if (SAVE_DECODING_RESULT) {
     // Output image data to file
-    std::string filePath = "/sdcard/Documents/decoded_from_p010_jpeg_input.rgb10";
+    std::string filePath = "/sdcard/Documents/decoded_from_p010_jpeg_input.rgb";
     std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
     if (!imageFile.is_open()) {
       ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
@@ -476,7 +476,7 @@
 
   JpegRBenchmark benchmark;
 
-  jpegr_metadata metadata = { .version = 1,
+  jpegr_metadata_struct metadata = { .version = 1,
                               .maxContentBoost = 8.0f,
                               .minContentBoost = 1.0f / 8.0f };
 
diff --git a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
index 6c61ff1..cf6a034 100644
--- a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
+++ b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
@@ -554,8 +554,8 @@
 
 TEST_F(RecoveryMapMathTest, applyRecoveryLUT) {
   for (int boost = 1; boost <= 10; boost++) {
-    jpegr_metadata metadata = { .maxContentBoost = static_cast<float>(boost),
-                                .minContentBoost = 1.0f / static_cast<float>(boost) };
+    jpegr_metadata_struct metadata = { .maxContentBoost = static_cast<float>(boost),
+                                       .minContentBoost = 1.0f / static_cast<float>(boost) };
     RecoveryLUT recoveryLUT(&metadata);
     for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) {
       float value = static_cast<float>(idx) / static_cast<float>(kRecoveryFactorNumEntries - 1);
@@ -573,8 +573,8 @@
   }
 
   for (int boost = 1; boost <= 10; boost++) {
-    jpegr_metadata metadata = { .maxContentBoost = static_cast<float>(boost),
-                                .minContentBoost = 1.0f };
+    jpegr_metadata_struct metadata = { .maxContentBoost = static_cast<float>(boost),
+                                       .minContentBoost = 1.0f };
     RecoveryLUT recoveryLUT(&metadata);
     for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) {
       float value = static_cast<float>(idx) / static_cast<float>(kRecoveryFactorNumEntries - 1);
@@ -592,8 +592,8 @@
   }
 
   for (int boost = 1; boost <= 10; boost++) {
-    jpegr_metadata metadata = { .maxContentBoost = static_cast<float>(boost),
-                                .minContentBoost = 1.0f / pow(static_cast<float>(boost),
+    jpegr_metadata_struct metadata = { .maxContentBoost = static_cast<float>(boost),
+                                       .minContentBoost = 1.0f / pow(static_cast<float>(boost),
                                                               1.0f / 3.0f) };
     RecoveryLUT recoveryLUT(&metadata);
     for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) {
@@ -659,8 +659,8 @@
 }
 
 TEST_F(RecoveryMapMathTest, EncodeRecovery) {
-  jpegr_metadata metadata = { .maxContentBoost = 4.0f,
-                              .minContentBoost = 1.0f / 4.0f };
+  jpegr_metadata_struct metadata = { .maxContentBoost = 4.0f,
+                                     .minContentBoost = 1.0f / 4.0f };
 
   EXPECT_EQ(encodeRecovery(0.0f, 0.0f, &metadata), 127);
   EXPECT_EQ(encodeRecovery(0.0f, 1.0f, &metadata), 127);
@@ -717,8 +717,8 @@
 }
 
 TEST_F(RecoveryMapMathTest, ApplyRecovery) {
-  jpegr_metadata metadata = { .maxContentBoost = 4.0f,
-                              .minContentBoost = 1.0f / 4.0f };
+  jpegr_metadata_struct metadata = { .maxContentBoost = 4.0f,
+                                     .minContentBoost = 1.0f / 4.0f };
 
   EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.0f, &metadata), RgbBlack());
   EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.5f, &metadata), RgbBlack());
@@ -981,8 +981,8 @@
 }
 
 TEST_F(RecoveryMapMathTest, ApplyMap) {
-  jpegr_metadata metadata = { .maxContentBoost = 8.0f,
-                              .minContentBoost = 1.0f / 8.0f };
+  jpegr_metadata_struct metadata = { .maxContentBoost = 8.0f,
+                                     .minContentBoost = 1.0f / 8.0f };
 
   EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata),
                 RgbWhite() * 8.0f);
diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp
index b7b2926..5306529 100644
--- a/libs/nativewindow/ANativeWindow.cpp
+++ b/libs/nativewindow/ANativeWindow.cpp
@@ -24,14 +24,49 @@
 
 #include <private/android/AHardwareBufferHelpers.h>
 
+#include <android/binder_libbinder.h>
+#include <dlfcn.h>
 #include <log/log.h>
 #include <ui/GraphicBuffer.h>
-#include <gui/Surface.h>
-#include <gui/view/Surface.h>
-#include <android/binder_libbinder.h>
 
 using namespace android;
 
+#if defined(__ANDROID_APEX__) || defined(__ANDROID_VNDK__)
+#error libnativewindow can only be built for system
+#endif
+
+using android_view_Surface_writeToParcel = status_t (*)(ANativeWindow* _Nonnull window,
+                                                        Parcel* _Nonnull parcel);
+
+using android_view_Surface_readFromParcel =
+        status_t (*)(const Parcel* _Nonnull parcel, ANativeWindow* _Nullable* _Nonnull outWindow);
+
+struct SurfaceParcelables {
+    android_view_Surface_writeToParcel write = nullptr;
+    android_view_Surface_readFromParcel read = nullptr;
+};
+
+const SurfaceParcelables* getSurfaceParcelFunctions() {
+    static SurfaceParcelables funcs = []() -> SurfaceParcelables {
+        SurfaceParcelables ret;
+        void* dl = dlopen("libgui.so", RTLD_NOW);
+        LOG_ALWAYS_FATAL_IF(!dl, "Failed to find libgui.so");
+        ret.write =
+                (android_view_Surface_writeToParcel)dlsym(dl, "android_view_Surface_writeToParcel");
+        LOG_ALWAYS_FATAL_IF(!ret.write,
+                            "libgui.so missing android_view_Surface_writeToParcel; "
+                            "loaded wrong libgui?");
+        ret.read =
+                (android_view_Surface_readFromParcel)dlsym(dl,
+                                                           "android_view_Surface_readFromParcel");
+        LOG_ALWAYS_FATAL_IF(!ret.read,
+                            "libgui.so missing android_view_Surface_readFromParcel; "
+                            "loaded wrong libgui?");
+        return ret;
+    }();
+    return &funcs;
+}
+
 static int32_t query(ANativeWindow* window, int what) {
     int value;
     int res = window->query(window, what, &value);
@@ -64,13 +99,6 @@
             return false;
     }
 }
-static sp<IGraphicBufferProducer> IGraphicBufferProducer_from_ANativeWindow(ANativeWindow* window) {
-    return Surface::getIGraphicBufferProducer(window);
-}
-
-static sp<IBinder> SurfaceControlHandle_from_ANativeWindow(ANativeWindow* window) {
-    return Surface::getSurfaceControlHandle(window);
-}
 
 /**************************************************************************************************
  * NDK
@@ -355,38 +383,24 @@
 
 binder_status_t ANativeWindow_readFromParcel(
         const AParcel* _Nonnull parcel, ANativeWindow* _Nullable* _Nonnull outWindow) {
-    const Parcel* nativeParcel = AParcel_viewPlatformParcel(parcel);
-
-    // Use a android::view::Surface to unparcel the window
-    std::shared_ptr<android::view::Surface> shimSurface = std::shared_ptr<android::view::Surface>();
-    status_t ret = shimSurface->readFromParcel(nativeParcel);
-    if (ret != OK) {
-        ALOGE("%s: Error: Failed to create android::view::Surface from AParcel", __FUNCTION__);
-        return STATUS_BAD_VALUE;
+    auto funcs = getSurfaceParcelFunctions();
+    if (funcs->read == nullptr) {
+        ALOGE("Failed to load Surface_readFromParcel implementation");
+        return STATUS_FAILED_TRANSACTION;
     }
-    sp<Surface> surface = sp<Surface>::make(
-            shimSurface->graphicBufferProducer, false, shimSurface->surfaceControlHandle);
-    ANativeWindow* anw = surface.get();
-    ANativeWindow_acquire(anw);
-    *outWindow = anw;
-    return STATUS_OK;
+    const Parcel* nativeParcel = AParcel_viewPlatformParcel(parcel);
+    return funcs->read(nativeParcel, outWindow);
 }
 
 binder_status_t ANativeWindow_writeToParcel(
         ANativeWindow* _Nonnull window, AParcel* _Nonnull parcel) {
-    int value;
-    int err = (*window->query)(window, NATIVE_WINDOW_CONCRETE_TYPE, &value);
-    if (err != OK || value != NATIVE_WINDOW_SURFACE) {
-        ALOGE("Error: ANativeWindow is not backed by Surface");
-        return STATUS_BAD_VALUE;
+    auto funcs = getSurfaceParcelFunctions();
+    if (funcs->write == nullptr) {
+        ALOGE("Failed to load Surface_writeToParcel implementation");
+        return STATUS_FAILED_TRANSACTION;
     }
-    // Use a android::view::Surface to parcelize the window
-    std::shared_ptr<android::view::Surface> shimSurface = std::shared_ptr<android::view::Surface>();
-    shimSurface->graphicBufferProducer = IGraphicBufferProducer_from_ANativeWindow(window);
-    shimSurface->surfaceControlHandle = SurfaceControlHandle_from_ANativeWindow(window);
-
     Parcel* nativeParcel = AParcel_viewPlatformParcel(parcel);
-    return shimSurface->writeToParcel(nativeParcel);
+    return funcs->write(window, nativeParcel);
 }
 
 /**************************************************************************************************
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index 5965d41..e393fb2 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -395,10 +395,14 @@
         mRenderEngineType != RenderEngineType::SKIA_VK_THREADED) {
         return;
     }
-    // We currently don't attempt to map a buffer if the buffer contains protected content
-    // because GPU resources for protected buffers is much more limited.
+    // We don't attempt to map a buffer if the buffer contains protected content. In GL this is
+    // important because GPU resources for protected buffers are much more limited. (In Vk we
+    // simply match the existing behavior for protected buffers.)  In Vk, we never cache any
+    // buffers while in a protected context, since Vk cannot share across contexts, and protected
+    // is less common.
     const bool isProtectedBuffer = buffer->getUsage() & GRALLOC_USAGE_PROTECTED;
-    if (isProtectedBuffer) {
+    if (isProtectedBuffer ||
+        (mRenderEngineType == RenderEngineType::SKIA_VK_THREADED && isProtected())) {
         return;
     }
     ATRACE_CALL();
@@ -461,6 +465,20 @@
     }
 }
 
+std::shared_ptr<AutoBackendTexture::LocalRef> SkiaRenderEngine::getOrCreateBackendTexture(
+        const sp<GraphicBuffer>& buffer, bool isOutputBuffer) {
+    // Do not lookup the buffer in the cache for protected contexts with the SkiaVk back-end
+    if (mRenderEngineType == RenderEngineType::SKIA_GL_THREADED ||
+        (mRenderEngineType == RenderEngineType::SKIA_VK_THREADED && !isProtected())) {
+        if (const auto& it = mTextureCache.find(buffer->getId()); it != mTextureCache.end()) {
+            return it->second;
+        }
+    }
+    return std::make_shared<AutoBackendTexture::LocalRef>(getActiveGrContext(),
+                                                          buffer->toAHardwareBuffer(),
+                                                          isOutputBuffer, mTextureCleanupMgr);
+}
+
 bool SkiaRenderEngine::canSkipPostRenderCleanup() const {
     std::lock_guard<std::mutex> lock(mRenderingMutex);
     return mTextureCleanupMgr.isEmpty();
@@ -651,21 +669,11 @@
     validateOutputBufferUsage(buffer->getBuffer());
 
     auto grContext = getActiveGrContext();
-    auto& cache = mTextureCache;
 
     // any AutoBackendTexture deletions will now be deferred until cleanupPostRender is called
     DeferTextureCleanup dtc(mTextureCleanupMgr);
 
-    std::shared_ptr<AutoBackendTexture::LocalRef> surfaceTextureRef;
-    if (const auto& it = cache.find(buffer->getBuffer()->getId()); it != cache.end()) {
-        surfaceTextureRef = it->second;
-    } else {
-        surfaceTextureRef =
-                std::make_shared<AutoBackendTexture::LocalRef>(grContext,
-                                                               buffer->getBuffer()
-                                                                       ->toAHardwareBuffer(),
-                                                               true, mTextureCleanupMgr);
-    }
+    auto surfaceTextureRef = getOrCreateBackendTexture(buffer->getBuffer(), true);
 
     // wait on the buffer to be ready to use prior to using it
     waitFence(grContext, bufferFence);
@@ -904,21 +912,7 @@
             ATRACE_NAME("DrawImage");
             validateInputBufferUsage(layer.source.buffer.buffer->getBuffer());
             const auto& item = layer.source.buffer;
-            std::shared_ptr<AutoBackendTexture::LocalRef> imageTextureRef = nullptr;
-
-            if (const auto& iter = cache.find(item.buffer->getBuffer()->getId());
-                iter != cache.end()) {
-                imageTextureRef = iter->second;
-            } else {
-                // If we didn't find the image in the cache, then create a local ref but don't cache
-                // it. If we're using skia, we're guaranteed to run on a dedicated GPU thread so if
-                // we didn't find anything in the cache then we intentionally did not cache this
-                // buffer's resources.
-                imageTextureRef = std::make_shared<
-                        AutoBackendTexture::LocalRef>(grContext,
-                                                      item.buffer->getBuffer()->toAHardwareBuffer(),
-                                                      false, mTextureCleanupMgr);
-            }
+            auto imageTextureRef = getOrCreateBackendTexture(item.buffer->getBuffer(), false);
 
             // if the layer's buffer has a fence, then we must must respect the fence prior to using
             // the buffer.
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index dd6646b..e4406b4 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -133,6 +133,8 @@
     void unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) override final;
     bool canSkipPostRenderCleanup() const override final;
 
+    std::shared_ptr<AutoBackendTexture::LocalRef> getOrCreateBackendTexture(
+            const sp<GraphicBuffer>& buffer, bool isOutputBuffer) REQUIRES(mRenderingMutex);
     void initCanvas(SkCanvas* canvas, const DisplaySettings& display);
     void drawShadow(SkCanvas* canvas, const SkRRect& casterRRect,
                     const ShadowSettings& shadowSettings);
@@ -167,7 +169,9 @@
     // Number of external holders of ExternalTexture references, per GraphicBuffer ID.
     std::unordered_map<GraphicBufferId, int32_t> mGraphicBufferExternalRefs
             GUARDED_BY(mRenderingMutex);
-    // Cache of GL textures that we'll store per GraphicBuffer ID, shared between GPU contexts.
+    // For GL, this cache is shared between protected and unprotected contexts. For Vulkan, it is
+    // only used for the unprotected context, because Vulkan does not allow sharing between
+    // contexts, and protected is less common.
     std::unordered_map<GraphicBufferId, std::shared_ptr<AutoBackendTexture::LocalRef>> mTextureCache
             GUARDED_BY(mRenderingMutex);
     std::unordered_map<shaders::LinearEffect, sk_sp<SkRuntimeEffect>, shaders::LinearEffectHasher>
diff --git a/opengl/OWNERS b/opengl/OWNERS
index 379f763..3d60a1d 100644
--- a/opengl/OWNERS
+++ b/opengl/OWNERS
@@ -1,11 +1,6 @@
-abdolrashidi@google.com
-cclao@google.com
 chrisforbes@google.com
 cnorthrop@google.com
 ianelliott@google.com
 jessehall@google.com
-lfy@google.com
 lpy@google.com
-romanl@google.com
 vantablack@google.com
-yuxinhu@google.com
diff --git a/opengl/libs/EGL/egl_cache.cpp b/opengl/libs/EGL/egl_cache.cpp
index b00ee33..3dc93ee 100644
--- a/opengl/libs/EGL/egl_cache.cpp
+++ b/opengl/libs/EGL/egl_cache.cpp
@@ -110,38 +110,6 @@
         }
     }
 
-    // Check the device config to decide whether multifile should be used
-    if (base::GetBoolProperty("ro.egl.blobcache.multifile", false)) {
-        mMultifileMode = true;
-        ALOGV("Using multifile EGL blobcache");
-    }
-
-    // Allow forcing the mode for debug purposes
-    std::string mode = base::GetProperty("debug.egl.blobcache.multifile", "");
-    if (mode == "true") {
-        ALOGV("Forcing multifile cache due to debug.egl.blobcache.multifile == %s", mode.c_str());
-        mMultifileMode = true;
-    } else if (mode == "false") {
-        ALOGV("Forcing monolithic cache due to debug.egl.blobcache.multifile == %s", mode.c_str());
-        mMultifileMode = false;
-    }
-
-    if (mMultifileMode) {
-        mCacheByteLimit = static_cast<size_t>(
-                base::GetUintProperty<uint32_t>("ro.egl.blobcache.multifile_limit",
-                                                kMultifileCacheByteLimit));
-
-        // Check for a debug value
-        int debugCacheSize = base::GetIntProperty("debug.egl.blobcache.multifile_limit", -1);
-        if (debugCacheSize >= 0) {
-            ALOGV("Overriding cache limit %zu with %i from debug.egl.blobcache.multifile_limit",
-                  mCacheByteLimit, debugCacheSize);
-            mCacheByteLimit = debugCacheSize;
-        }
-
-        ALOGV("Using multifile EGL blobcache limit of %zu bytes", mCacheByteLimit);
-    }
-
     mInitialized = true;
 }
 
@@ -167,6 +135,8 @@
         return;
     }
 
+    updateMode();
+
     if (mInitialized) {
         if (mMultifileMode) {
             MultifileBlobCache* mbc = getMultifileBlobCacheLocked();
@@ -200,6 +170,8 @@
         return 0;
     }
 
+    updateMode();
+
     if (mInitialized) {
         if (mMultifileMode) {
             MultifileBlobCache* mbc = getMultifileBlobCacheLocked();
@@ -247,6 +219,51 @@
     return 0;
 }
 
+void egl_cache_t::updateMode() {
+    // We don't set the mode in the constructor because these checks have
+    // a non-trivial cost, and not all processes that instantiate egl_cache_t
+    // will use it.
+
+    // If we've already set the mode, skip these checks
+    static bool checked = false;
+    if (checked) {
+        return;
+    }
+    checked = true;
+
+    // Check the device config to decide whether multifile should be used
+    if (base::GetBoolProperty("ro.egl.blobcache.multifile", false)) {
+        mMultifileMode = true;
+        ALOGV("Using multifile EGL blobcache");
+    }
+
+    // Allow forcing the mode for debug purposes
+    std::string mode = base::GetProperty("debug.egl.blobcache.multifile", "");
+    if (mode == "true") {
+        ALOGV("Forcing multifile cache due to debug.egl.blobcache.multifile == %s", mode.c_str());
+        mMultifileMode = true;
+    } else if (mode == "false") {
+        ALOGV("Forcing monolithic cache due to debug.egl.blobcache.multifile == %s", mode.c_str());
+        mMultifileMode = false;
+    }
+
+    if (mMultifileMode) {
+        mCacheByteLimit = static_cast<size_t>(
+                base::GetUintProperty<uint32_t>("ro.egl.blobcache.multifile_limit",
+                                                kMultifileCacheByteLimit));
+
+        // Check for a debug value
+        int debugCacheSize = base::GetIntProperty("debug.egl.blobcache.multifile_limit", -1);
+        if (debugCacheSize >= 0) {
+            ALOGV("Overriding cache limit %zu with %i from debug.egl.blobcache.multifile_limit",
+                  mCacheByteLimit, debugCacheSize);
+            mCacheByteLimit = debugCacheSize;
+        }
+
+        ALOGV("Using multifile EGL blobcache limit of %zu bytes", mCacheByteLimit);
+    }
+}
+
 BlobCache* egl_cache_t::getBlobCacheLocked() {
     if (mBlobCache == nullptr) {
         mBlobCache.reset(new FileBlobCache(kMaxMonolithicKeySize, kMaxMonolithicValueSize,
diff --git a/opengl/libs/EGL/egl_cache.h b/opengl/libs/EGL/egl_cache.h
index 1399368..ae6d381 100644
--- a/opengl/libs/EGL/egl_cache.h
+++ b/opengl/libs/EGL/egl_cache.h
@@ -88,6 +88,9 @@
     egl_cache_t(const egl_cache_t&); // not implemented
     void operator=(const egl_cache_t&); // not implemented
 
+    // Check system properties to determine which blobcache mode should be used
+    void updateMode();
+
     // getBlobCacheLocked returns the BlobCache object being used to store the
     // key/value blob pairs.  If the BlobCache object has not yet been created,
     // this will do so, loading the serialized cache contents from disk if
diff --git a/opengl/libs/EGL/egl_display.cpp b/opengl/libs/EGL/egl_display.cpp
index 0b755aa..c2c856e 100644
--- a/opengl/libs/EGL/egl_display.cpp
+++ b/opengl/libs/EGL/egl_display.cpp
@@ -322,6 +322,16 @@
 
         mExtensionString = gBuiltinExtensionString;
 
+        // b/269060366 Conditionally enabled EGL_ANDROID_get_frame_timestamps extension if the
+        // device's present timestamps are reliable (which may not be the case on emulators).
+        if (cnx->useAngle) {
+            if (android::base::GetBoolProperty("service.sf.present_timestamp", false)) {
+                mExtensionString.append("EGL_ANDROID_get_frame_timestamps");
+            }
+        } else {
+            mExtensionString.append("EGL_ANDROID_get_frame_timestamps");
+        }
+
         hasColorSpaceSupport = findExtension(disp.queryString.extensions, "EGL_KHR_gl_colorspace");
 
         // Note: CDD requires that devices supporting wide color and/or HDR color also support
@@ -361,6 +371,7 @@
                     findExtension(disp.queryString.extensions, "EGL_KHR_image_gl_colorspace")) {
                     mExtensionString.append("EGL_EXT_image_gl_colorspace ");
                 }
+
                 if (findExtension(disp.queryString.extensions, ext.c_str(), len)) {
                     mExtensionString.append(ext + " ");
                 }
diff --git a/opengl/libs/EGL/egl_platform_entries.cpp b/opengl/libs/EGL/egl_platform_entries.cpp
index 0527c8a..2bca14d 100644
--- a/opengl/libs/EGL/egl_platform_entries.cpp
+++ b/opengl/libs/EGL/egl_platform_entries.cpp
@@ -84,7 +84,8 @@
 // Extensions implemented by the EGL wrapper.
 const char* const gBuiltinExtensionString =
         "EGL_ANDROID_front_buffer_auto_refresh "
-        "EGL_ANDROID_get_frame_timestamps "
+        // b/269060366 Conditionally enabled during display initialization:
+        //"EGL_ANDROID_get_frame_timestamps "
         "EGL_ANDROID_get_native_client_buffer "
         "EGL_ANDROID_presentation_time "
         "EGL_EXT_surface_CTA861_3_metadata "
@@ -2185,6 +2186,10 @@
         return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
     }
 
+    if (!dp->haveExtension("EGL_ANDROID_get_frame_timestamps")) {
+        return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
+    }
+
     SurfaceRef _s(dp, surface);
     if (!_s.get()) {
         return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE);
@@ -2218,6 +2223,10 @@
         return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
     }
 
+    if (!dp->haveExtension("EGL_ANDROID_get_frame_timestamps")) {
+        return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
+    }
+
     SurfaceRef _s(dp, surface);
     if (!_s.get()) {
         return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE);
@@ -2272,6 +2281,10 @@
         return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
     }
 
+    if (!dp->haveExtension("EGL_ANDROID_get_frame_timestamps")) {
+        return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
+    }
+
     SurfaceRef _s(dp, surface);
     if (!_s.get()) {
         return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE);
@@ -2302,6 +2315,10 @@
         return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
     }
 
+    if (!dp->haveExtension("EGL_ANDROID_get_frame_timestamps")) {
+        return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
+    }
+
     SurfaceRef _s(dp, surface);
     if (!_s.get()) {
         return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE);
@@ -2387,6 +2404,10 @@
         return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
     }
 
+    if (!dp->haveExtension("EGL_ANDROID_get_frame_timestamps")) {
+        return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
+    }
+
     SurfaceRef _s(dp, surface);
     if (!_s.get()) {
         return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE);
diff --git a/services/gpuservice/tests/unittests/Android.bp b/services/gpuservice/tests/unittests/Android.bp
index 86f6c7f..51642f9 100644
--- a/services/gpuservice/tests/unittests/Android.bp
+++ b/services/gpuservice/tests/unittests/Android.bp
@@ -24,9 +24,6 @@
 cc_test {
     name: "gpuservice_unittest",
     test_suites: ["device-tests"],
-    sanitize: {
-        address: true,
-    },
     srcs: [
         "GpuMemTest.cpp",
         "GpuMemTracerTest.cpp",
diff --git a/services/gpuservice/vts/OWNERS b/services/gpuservice/vts/OWNERS
index e789052..a63de1c 100644
--- a/services/gpuservice/vts/OWNERS
+++ b/services/gpuservice/vts/OWNERS
@@ -1,4 +1,5 @@
 # Bug component: 653544
+kocdemir@google.com
 paulthomson@google.com
 pbaiget@google.com
 lfy@google.com
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 43cac05..cd427f0 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -110,6 +110,8 @@
 constexpr int LOGTAG_INPUT_FOCUS = 62001;
 constexpr int LOGTAG_INPUT_CANCEL = 62003;
 
+const ui::Transform kIdentityTransform;
+
 inline nsecs_t now() {
     return systemTime(SYSTEM_TIME_MONOTONIC);
 }
@@ -475,8 +477,8 @@
 }
 
 // Returns true if the given window can accept pointer events at the given display location.
-bool windowAcceptsTouchAt(const WindowInfo& windowInfo, int32_t displayId, int32_t x, int32_t y,
-                          bool isStylus) {
+bool windowAcceptsTouchAt(const WindowInfo& windowInfo, int32_t displayId, float x, float y,
+                          bool isStylus, const ui::Transform& displayTransform) {
     const auto inputConfig = windowInfo.inputConfig;
     if (windowInfo.displayId != displayId ||
         inputConfig.test(WindowInfo::InputConfig::NOT_VISIBLE)) {
@@ -486,7 +488,17 @@
     if (inputConfig.test(WindowInfo::InputConfig::NOT_TOUCHABLE) && !windowCanInterceptTouch) {
         return false;
     }
-    if (!windowInfo.touchableRegionContainsPoint(x, y)) {
+
+    // Window Manager works in the logical display coordinate space. When it specifies bounds for a
+    // window as (l, t, r, b), the range of x in [l, r) and y in [t, b) are considered to be inside
+    // the window. Points on the right and bottom edges should not be inside the window, so we need
+    // to be careful about performing a hit test when the display is rotated, since the "right" and
+    // "bottom" of the window will be different in the display (un-rotated) space compared to in the
+    // logical display in which WM determined the bounds. Perform the hit test in the logical
+    // display space to ensure these edges are considered correctly in all orientations.
+    const auto touchableRegion = displayTransform.transform(windowInfo.touchableRegion);
+    const auto p = displayTransform.transform(x, y);
+    if (!touchableRegion.contains(std::floor(p.x), std::floor(p.y))) {
         return false;
     }
     return true;
@@ -540,19 +552,16 @@
     return {};
 }
 
-Point resolveTouchedPosition(const MotionEntry& entry) {
+std::pair<float, float> resolveTouchedPosition(const MotionEntry& entry) {
     const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE);
     // Always dispatch mouse events to cursor position.
     if (isFromMouse) {
-        return Point(static_cast<int32_t>(entry.xCursorPosition),
-                     static_cast<int32_t>(entry.yCursorPosition));
+        return {entry.xCursorPosition, entry.yCursorPosition};
     }
 
     const int32_t pointerIndex = getMotionEventActionPointerIndex(entry.action);
-    return Point(static_cast<int32_t>(
-                         entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_X)),
-                 static_cast<int32_t>(
-                         entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_Y)));
+    return {entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_X),
+            entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_Y)};
 }
 
 std::optional<nsecs_t> getDownTime(const EventEntry& eventEntry) {
@@ -1159,7 +1168,7 @@
 }
 
 std::pair<sp<WindowInfoHandle>, std::vector<InputTarget>>
-InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, int32_t x, int32_t y, bool isStylus,
+InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, float x, float y, bool isStylus,
                                            bool ignoreDragWindow) const {
     // Traverse windows from front to back to find touched window.
     std::vector<InputTarget> outsideTargets;
@@ -1170,7 +1179,8 @@
         }
 
         const WindowInfo& info = *windowHandle->getInfo();
-        if (!info.isSpy() && windowAcceptsTouchAt(info, displayId, x, y, isStylus)) {
+        if (!info.isSpy() &&
+            windowAcceptsTouchAt(info, displayId, x, y, isStylus, getTransformLocked(displayId))) {
             return {windowHandle, outsideTargets};
         }
 
@@ -1184,14 +1194,14 @@
 }
 
 std::vector<sp<WindowInfoHandle>> InputDispatcher::findTouchedSpyWindowsAtLocked(
-        int32_t displayId, int32_t x, int32_t y, bool isStylus) const {
+        int32_t displayId, float x, float y, bool isStylus) const {
     // Traverse windows from front to back and gather the touched spy windows.
     std::vector<sp<WindowInfoHandle>> spyWindows;
     const auto& windowHandles = getWindowHandlesLocked(displayId);
     for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
         const WindowInfo& info = *windowHandle->getInfo();
 
-        if (!windowAcceptsTouchAt(info, displayId, x, y, isStylus)) {
+        if (!windowAcceptsTouchAt(info, displayId, x, y, isStylus, getTransformLocked(displayId))) {
             continue;
         }
         if (!info.isSpy()) {
@@ -2231,8 +2241,7 @@
         }
         // Handle the case where we did not find a window.
         if (newTouchedWindowHandle == nullptr) {
-            ALOGD("No new touched window at (%" PRId32 ", %" PRId32 ") in display %" PRId32, x, y,
-                  displayId);
+            ALOGD("No new touched window at (%.1f, %.1f) in display %" PRId32, x, y, displayId);
             // Try to assign the pointer to the first foreground window we find, if there is one.
             newTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle();
         }
@@ -2270,7 +2279,8 @@
         }
 
         if (newTouchedWindows.empty()) {
-            ALOGI("Dropping event because there is no touchable window at (%d, %d) on display %d.",
+            ALOGI("Dropping event because there is no touchable window at (%.1f, %.1f) on display "
+                  "%d.",
                   x, y, displayId);
             outInjectionResult = InputEventInjectionResult::FAILED;
             return {};
@@ -4036,18 +4046,20 @@
 
 /**
  * If one of the meta shortcuts is detected, process them here:
- *     Meta + Backspace -> generate BACK
- *     Meta + Enter -> generate HOME
- * This will potentially overwrite keyCode and metaState.
+ *     Meta + Backspace; Meta + Grave; Meta + Left arrow -> generate BACK
+ * Most System shortcuts are handled in PhoneWindowManager.java except 'Back' shortcuts. Unlike
+ * Back, other shortcuts DO NOT need to be sent to applications and are fully handled by the system.
+ * But for Back key and Back shortcuts, we need to send KEYCODE_BACK to applications which can
+ * potentially handle the back key presses.
+ * Note: We don't send any Meta based KeyEvents to applications, so we need to convert to a KeyEvent
+ * where meta modifier is off before sending. Currently only use case is 'Back'.
  */
 void InputDispatcher::accelerateMetaShortcuts(const int32_t deviceId, const int32_t action,
                                               int32_t& keyCode, int32_t& metaState) {
     if (metaState & AMETA_META_ON && action == AKEY_EVENT_ACTION_DOWN) {
         int32_t newKeyCode = AKEYCODE_UNKNOWN;
-        if (keyCode == AKEYCODE_DEL) {
+        if (keyCode == AKEYCODE_DEL || keyCode == AKEYCODE_GRAVE || keyCode == AKEYCODE_DPAD_LEFT) {
             newKeyCode = AKEYCODE_BACK;
-        } else if (keyCode == AKEYCODE_ENTER) {
-            newKeyCode = AKEYCODE_HOME;
         }
         if (newKeyCode != AKEYCODE_UNKNOWN) {
             std::scoped_lock _l(mLock);
@@ -4178,9 +4190,12 @@
                   args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION));
         }
     }
-    LOG_ALWAYS_FATAL_IF(!validateMotionEvent(args->action, args->actionButton, args->pointerCount,
-                                             args->pointerProperties),
-                        "Invalid event: %s", args->dump().c_str());
+
+    if (!validateMotionEvent(args->action, args->actionButton, args->pointerCount,
+                             args->pointerProperties)) {
+        LOG(ERROR) << "Invalid event: " << args->dump();
+        return;
+    }
 
     uint32_t policyFlags = args->policyFlags;
     policyFlags |= POLICY_FLAG_TRUSTED;
@@ -4759,6 +4774,12 @@
     return getWindowHandleLocked(focusedToken, displayId);
 }
 
+ui::Transform InputDispatcher::getTransformLocked(int32_t displayId) const {
+    auto displayInfoIt = mDisplayInfos.find(displayId);
+    return displayInfoIt != mDisplayInfos.end() ? displayInfoIt->second.transform
+                                                : kIdentityTransform;
+}
+
 bool InputDispatcher::canWindowReceiveMotionLocked(const sp<WindowInfoHandle>& window,
                                                    const MotionEntry& motionEntry) const {
     const WindowInfo& info = *window->getInfo();
@@ -4796,7 +4817,7 @@
     TouchOcclusionInfo occlusionInfo = computeTouchOcclusionInfoLocked(window, x, y);
     if (!isTouchTrustedLocked(occlusionInfo)) {
         if (DEBUG_TOUCH_OCCLUSION) {
-            ALOGD("Stack of obscuring windows during untrusted touch (%d, %d):", x, y);
+            ALOGD("Stack of obscuring windows during untrusted touch (%.1f, %.1f):", x, y);
             for (const auto& log : occlusionInfo.debugInfo) {
                 ALOGD("%s", log.c_str());
             }
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index b94858b..2246d47 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -239,11 +239,11 @@
     std::shared_ptr<EventEntry> mNextUnblockedEvent GUARDED_BY(mLock);
 
     std::pair<sp<android::gui::WindowInfoHandle>, std::vector<InputTarget>>
-    findTouchedWindowAtLocked(int32_t displayId, int32_t x, int32_t y, bool isStylus = false,
+    findTouchedWindowAtLocked(int32_t displayId, float x, float y, bool isStylus = false,
                               bool ignoreDragWindow = false) const REQUIRES(mLock);
 
     std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAtLocked(
-            int32_t displayId, int32_t x, int32_t y, bool isStylus) const REQUIRES(mLock);
+            int32_t displayId, float x, float y, bool isStylus) const REQUIRES(mLock);
 
     sp<android::gui::WindowInfoHandle> findTouchedForegroundWindowLocked(int32_t displayId) const
             REQUIRES(mLock);
@@ -374,6 +374,7 @@
             int32_t displayId) const REQUIRES(mLock);
     sp<android::gui::WindowInfoHandle> getWindowHandleLocked(
             const sp<IBinder>& windowHandleToken) const REQUIRES(mLock);
+    ui::Transform getTransformLocked(int32_t displayId) const REQUIRES(mLock);
 
     // Same function as above, but faster. Since displayId is provided, this avoids the need
     // to loop through all displays.
diff --git a/services/inputflinger/docs/input_coordinates.md b/services/inputflinger/docs/input_coordinates.md
new file mode 100644
index 0000000..7795710
--- /dev/null
+++ b/services/inputflinger/docs/input_coordinates.md
@@ -0,0 +1,113 @@
+# Input Coordinate Processing in InputFlinger
+
+This document aims to illustrate why we need to take care when converting
+between the discrete and continuous coordinate spaces, especially when
+performing rotations.
+
+The Linux evdev protocol works over **discrete integral** values. The same is
+true for displays, which output discrete pixels. WindowManager also tracks
+window bounds in pixels in the rotated logical display.
+
+However, our `MotionEvent` APIs
+report **floating point** axis values in a **continuous space**. This disparity
+is important to note when working in InputFlinger, which has to make sure the
+discrete raw coordinates are  converted to the continuous space correctly in all
+scenarios.
+
+## Disparity between continuous and discrete coordinates during rotation
+
+Let's consider an example of device that has a 3 x 4 screen.
+
+### Natural orientation:  No rotation
+
+If the user interacts with the highlighted pixel, the touchscreen would report
+the discreet coordinates (0, 2).
+
+```
+     ┌─────┬─────┬─────┐
+     │ 0,0 │ 1,0 │ 2,0 │
+     ├─────┼─────┼─────┤
+     │ 0,1 │ 1,1 │ 2,1 │
+     ├─────┼─────┼─────┤
+     │█0,2█│ 1,2 │ 2,2 │
+     ├─────┼─────┼─────┤
+     │ 0,3 │ 1,3 │ 2,3 │
+     └─────┴─────┴─────┘
+```
+
+When converted to the continuous space, the point (0, 2) corresponds to the
+location shown below.
+
+```
+     0     1     2     3
+  0  ┌─────┬─────┬─────┐
+     │     │     │     │
+  1  ├─────┼─────┼─────┤
+     │     │     │     │
+  2  █─────┼─────┼─────┤
+     │     │     │     │
+  3  ├─────┼─────┼─────┤
+     │     │     │     │
+  4  └─────┴─────┴─────┘
+```
+
+### Rotated orientation: 90-degree counter-clockwise rotation
+
+When the device is rotated and the same place on the touchscreen is touched, the
+input device will still report the same coordinates of (0, 2).
+
+In the rotated display, that now corresponds to the pixel (2, 2).
+
+```
+     ┌─────┬─────┬─────┬─────┐
+     │ 0,0 │ 1,0 │ 2,0 │ 3,0 │
+     ├─────┼─────┼─────┼─────┤
+     │ 0,1 │ 1,1 │ 2,1 │ 3,1 │
+     ├─────┼─────┼─────┼─────┤
+     │ 0,2 │ 1,2 │█2,2█│ 3,2 │
+     └─────┴─────┴─────┴─────┘
+```
+
+*It is important to note that rotating the device 90 degrees is NOT equivalent
+to rotating the continuous coordinate space by 90 degrees.*
+
+The point (2, 2) now corresponds to a different location in the continuous space
+than before, even though the user was interacting at the same place on the
+touchscreen.
+
+```
+     0     1     2     3     4
+  0  ┌─────┬─────┬─────┬─────┐
+     │     │     │     │     │
+  1  ├─────┼─────┼─────┼─────┤
+     │     │     │     │     │
+  2  ├─────┼─────█─────┼─────┤
+     │     │     │     │     │
+  3  └─────┴─────┴─────┴─────┘
+```
+
+If we were to simply (incorrectly) rotate the continuous space from before by
+90 degrees, the touched point would correspond to the location (2, 3), shown
+below. This new point is outside the bounds of the display, since it does not
+correspond to any pixel at that location.
+
+It should be impossible for a touchscreen to generate points outside the bounds
+of the display, because we assume that the area of the touchscreen maps directly
+to the area of the display. Therefore, that point is an invalid coordinate that
+cannot be generated by an input device.
+
+```
+     0     1     2     3     4
+  0  ┌─────┬─────┬─────┬─────┐
+     │     │     │     │     ╏
+  1  ├─────┼─────┼─────┼─────┤
+     │     │     │     │     ╏
+  2  ├─────┼─────┼─────┼─────┤
+     │     │     │     │     ╏
+  3  └-----┴-----█-----┴-----┘
+```
+
+The same logic applies to windows as well. When performing hit tests to
+determine if a point in the continuous space falls inside a window's bounds,
+hit test must be performed in the correct orientation, since points on the right
+and bottom edges of the window do not fall within the window bounds.
diff --git a/services/inputflinger/host/InputDriver.cpp b/services/inputflinger/host/InputDriver.cpp
index 97d57e4..ec0388d 100644
--- a/services/inputflinger/host/InputDriver.cpp
+++ b/services/inputflinger/host/InputDriver.cpp
@@ -242,13 +242,13 @@
 
 input_property_t* InputDriver::inputGetDeviceProperty(input_property_map_t* map, const char* key) {
     if (map != nullptr) {
-        std::string value;
-        auto prop = std::make_unique<input_property_t>();
-        if (!map->propertyMap->tryGetProperty(key, value)) {
+        std::optional<std::string> value = map->propertyMap->getString(key);
+        if (!value.has_value()) {
             return nullptr;
         }
+        auto prop = std::make_unique<input_property_t>();
         prop->key = key;
-        prop->value = value.c_str();
+        prop->value = value->c_str();
         return prop.release();
     }
     return nullptr;
diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h
index 9dbdd5a..95f819a 100644
--- a/services/inputflinger/include/PointerControllerInterface.h
+++ b/services/inputflinger/include/PointerControllerInterface.h
@@ -22,6 +22,20 @@
 
 namespace android {
 
+struct FloatPoint {
+    float x;
+    float y;
+
+    inline FloatPoint(float x, float y) : x(x), y(y) {}
+
+    inline explicit FloatPoint(vec2 p) : x(p.x), y(p.y) {}
+
+    template <typename T, typename U>
+    operator std::tuple<T, U>() {
+        return {x, y};
+    }
+};
+
 /**
  * Interface for tracking a mouse / touch pad pointer and touch pad spots.
  *
@@ -40,23 +54,16 @@
 public:
     /* Gets the bounds of the region that the pointer can traverse.
      * Returns true if the bounds are available. */
-    virtual bool getBounds(float* outMinX, float* outMinY,
-            float* outMaxX, float* outMaxY) const = 0;
+    virtual std::optional<FloatRect> getBounds() const = 0;
 
     /* Move the pointer. */
     virtual void move(float deltaX, float deltaY) = 0;
 
-    /* Sets a mask that indicates which buttons are pressed. */
-    virtual void setButtonState(int32_t buttonState) = 0;
-
-    /* Gets a mask that indicates which buttons are pressed. */
-    virtual int32_t getButtonState() const = 0;
-
     /* Sets the absolute location of the pointer. */
     virtual void setPosition(float x, float y) = 0;
 
     /* Gets the absolute location of the pointer. */
-    virtual void getPosition(float* outX, float* outY) const = 0;
+    virtual FloatPoint getPosition() const = 0;
 
     enum class Transition {
         // Fade/unfade immediately.
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index d3d8021..e65f3af 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -52,6 +52,7 @@
 #include <utils/Timers.h>
 
 #include <filesystem>
+#include <optional>
 #include <regex>
 #include <utility>
 
@@ -673,9 +674,9 @@
 
 bool EventHub::Device::isExternalDeviceLocked() {
     if (configuration) {
-        bool value;
-        if (configuration->tryGetProperty("device.internal", value)) {
-            return !value;
+        std::optional<bool> isInternal = configuration->getBool("device.internal");
+        if (isInternal.has_value()) {
+            return !isInternal.value();
         }
     }
     return identifier.bus == BUS_USB || identifier.bus == BUS_BLUETOOTH;
@@ -683,9 +684,9 @@
 
 bool EventHub::Device::deviceHasMicLocked() {
     if (configuration) {
-        bool value;
-        if (configuration->tryGetProperty("audio.mic", value)) {
-            return value;
+        std::optional<bool> hasMic = configuration->getBool("audio.mic");
+        if (hasMic.has_value()) {
+            return hasMic.value();
         }
     }
     return false;
@@ -882,14 +883,13 @@
     return device != nullptr ? device->controllerNumber : 0;
 }
 
-void EventHub::getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const {
+std::optional<PropertyMap> EventHub::getConfiguration(int32_t deviceId) const {
     std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
-    if (device != nullptr && device->configuration) {
-        *outConfiguration = *device->configuration;
-    } else {
-        outConfiguration->clear();
+    if (device == nullptr || device->configuration == nullptr) {
+        return {};
     }
+    return *device->configuration;
 }
 
 status_t EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis,
@@ -2282,8 +2282,8 @@
     }
 
     // See if the device is specially configured to be of a certain type.
-    std::string deviceType;
-    if (device->configuration && device->configuration->tryGetProperty("device.type", deviceType)) {
+    if (device->configuration) {
+        std::string deviceType = device->configuration->getString("device.type").value_or("");
         if (deviceType == "rotaryEncoder") {
             device->classes |= InputDeviceClass::ROTARY_ENCODER;
         } else if (deviceType == "externalStylus") {
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 9fe652c..69363b6 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -210,14 +210,13 @@
     // Touchscreens and touchpad devices.
     static const bool ENABLE_TOUCHPAD_GESTURES_LIBRARY =
             sysprop::InputProperties::enable_touchpad_gestures_library().value_or(true);
-    // TODO(b/246587538): Fix the new touchpad stack for Sony DualShock 4 (5c4, 9cc) and DualSense
-    // (ce6) touchpads, or at least load this setting from the IDC file.
+    // TODO(b/272518665): Fix the new touchpad stack for Sony DualShock 4 (5c4, 9cc) touchpads, or
+    // at least load this setting from the IDC file.
     const InputDeviceIdentifier identifier = contextPtr->getDeviceIdentifier();
-    const bool isSonyGamepadTouchpad = identifier.vendor == 0x054c &&
-            (identifier.product == 0x05c4 || identifier.product == 0x09cc ||
-             identifier.product == 0x0ce6);
+    const bool isSonyDualShock4Touchpad = identifier.vendor == 0x054c &&
+            (identifier.product == 0x05c4 || identifier.product == 0x09cc);
     if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) &&
-        classes.test(InputDeviceClass::TOUCH_MT) && !isSonyGamepadTouchpad) {
+        classes.test(InputDeviceClass::TOUCH_MT) && !isSonyDualShock4Touchpad) {
         mappers.push_back(std::make_unique<TouchpadInputMapper>(*contextPtr));
     } else if (classes.test(InputDeviceClass::TOUCH_MT)) {
         mappers.push_back(std::make_unique<MultiTouchInputMapper>(*contextPtr));
@@ -284,9 +283,11 @@
         if (!changes || (changes & InputReaderConfiguration::CHANGE_DEVICE_TYPE)) {
             mConfiguration.clear();
             for_each_subdevice([this](InputDeviceContext& context) {
-                PropertyMap configuration;
-                context.getConfiguration(&configuration);
-                mConfiguration.addAll(&configuration);
+                std::optional<PropertyMap> configuration =
+                        getEventHub()->getConfiguration(context.getEventHubId());
+                if (configuration) {
+                    mConfiguration.addAll(&(*configuration));
+                }
             });
 
             mAssociatedDeviceType =
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 86acadb..0b15efe 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -263,7 +263,13 @@
 
     virtual int32_t getDeviceControllerNumber(int32_t deviceId) const = 0;
 
-    virtual void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const = 0;
+    /**
+     * Get the PropertyMap for the provided EventHub device, if available.
+     * This acquires the device lock, so a copy is returned rather than the raw pointer
+     * to the device's PropertyMap. A std::nullopt may be returned if the device could
+     * not be found, or if it doesn't have any configuration.
+     */
+    virtual std::optional<PropertyMap> getConfiguration(int32_t deviceId) const = 0;
 
     virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
                                          RawAbsoluteAxisInfo* outAxisInfo) const = 0;
@@ -464,7 +470,7 @@
 
     int32_t getDeviceControllerNumber(int32_t deviceId) const override final;
 
-    void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const override final;
+    std::optional<PropertyMap> getConfiguration(int32_t deviceId) const override final;
 
     status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
                                  RawAbsoluteAxisInfo* outAxisInfo) const override final;
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 7867029..4ae06fe 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -269,9 +269,6 @@
     inline int32_t getDeviceControllerNumber() const {
         return mEventHub->getDeviceControllerNumber(mId);
     }
-    inline void getConfiguration(PropertyMap* outConfiguration) const {
-        return mEventHub->getConfiguration(mId, outConfiguration);
-    }
     inline status_t getAbsoluteAxisInfo(int32_t code, RawAbsoluteAxisInfo* axisInfo) const {
         return mEventHub->getAbsoluteAxisInfo(mId, code, axisInfo);
     }
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index 13e4d0c..b27f70c 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -20,6 +20,8 @@
 
 #include "CursorInputMapper.h"
 
+#include <optional>
+
 #include "CursorButtonAccumulator.h"
 #include "CursorScrollAccumulator.h"
 #include "PointerControllerInterface.h"
@@ -83,10 +85,11 @@
     InputMapper::populateDeviceInfo(info);
 
     if (mParameters.mode == Parameters::Mode::POINTER) {
-        float minX, minY, maxX, maxY;
-        if (mPointerController->getBounds(&minX, &minY, &maxX, &maxY)) {
-            info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, minX, maxX, 0.0f, 0.0f, 0.0f);
-            info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, minY, maxY, 0.0f, 0.0f, 0.0f);
+        if (const auto bounds = mPointerController->getBounds(); bounds) {
+            info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, bounds->left, bounds->right, 0.0f,
+                                 0.0f, 0.0f);
+            info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, bounds->top, bounds->bottom, 0.0f,
+                                 0.0f, 0.0f);
         }
     } else {
         info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, -1.0f, 1.0f, 0.0f, mXScale, 0.0f);
@@ -250,18 +253,17 @@
 
 void CursorInputMapper::configureParameters() {
     mParameters.mode = Parameters::Mode::POINTER;
-    std::string cursorModeString;
-    if (getDeviceContext().getConfiguration().tryGetProperty("cursor.mode", cursorModeString)) {
-        if (cursorModeString == "navigation") {
+    const PropertyMap& config = getDeviceContext().getConfiguration();
+    std::optional<std::string> cursorModeString = config.getString("cursor.mode");
+    if (cursorModeString.has_value()) {
+        if (*cursorModeString == "navigation") {
             mParameters.mode = Parameters::Mode::NAVIGATION;
-        } else if (cursorModeString != "pointer" && cursorModeString != "default") {
-            ALOGW("Invalid value for cursor.mode: '%s'", cursorModeString.c_str());
+        } else if (*cursorModeString != "pointer" && *cursorModeString != "default") {
+            ALOGW("Invalid value for cursor.mode: '%s'", cursorModeString->c_str());
         }
     }
 
-    mParameters.orientationAware = false;
-    getDeviceContext().getConfiguration().tryGetProperty("cursor.orientationAware",
-                                                         mParameters.orientationAware);
+    mParameters.orientationAware = config.getBool("cursor.orientationAware").value_or(false);
 
     mParameters.hasAssociatedDisplay = false;
     if (mParameters.mode == Parameters::Mode::POINTER || mParameters.orientationAware) {
@@ -371,15 +373,10 @@
             if (moved) {
                 mPointerController->move(deltaX, deltaY);
             }
-
-            if (buttonsChanged) {
-                mPointerController->setButtonState(currentButtonState);
-            }
-
             mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
         }
 
-        mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+        std::tie(xCursorPosition, yCursorPosition) = mPointerController->getPosition();
 
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index 0361bc6..f39e004 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -61,36 +61,6 @@
             scanCode >= BTN_WHEEL;
 }
 
-static bool isMediaKey(int32_t keyCode) {
-    switch (keyCode) {
-        case AKEYCODE_MEDIA_PLAY:
-        case AKEYCODE_MEDIA_PAUSE:
-        case AKEYCODE_MEDIA_PLAY_PAUSE:
-        case AKEYCODE_MUTE:
-        case AKEYCODE_HEADSETHOOK:
-        case AKEYCODE_MEDIA_STOP:
-        case AKEYCODE_MEDIA_NEXT:
-        case AKEYCODE_MEDIA_PREVIOUS:
-        case AKEYCODE_MEDIA_REWIND:
-        case AKEYCODE_MEDIA_RECORD:
-        case AKEYCODE_MEDIA_FAST_FORWARD:
-        case AKEYCODE_MEDIA_SKIP_FORWARD:
-        case AKEYCODE_MEDIA_SKIP_BACKWARD:
-        case AKEYCODE_MEDIA_STEP_FORWARD:
-        case AKEYCODE_MEDIA_STEP_BACKWARD:
-        case AKEYCODE_MEDIA_AUDIO_TRACK:
-        case AKEYCODE_VOLUME_UP:
-        case AKEYCODE_VOLUME_DOWN:
-        case AKEYCODE_VOLUME_MUTE:
-        case AKEYCODE_TV_AUDIO_DESCRIPTION:
-        case AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP:
-        case AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN:
-            return true;
-        default:
-            return false;
-    }
-}
-
 // --- KeyboardInputMapper ---
 
 KeyboardInputMapper::KeyboardInputMapper(InputDeviceContext& deviceContext, uint32_t source,
@@ -184,15 +154,10 @@
 }
 
 void KeyboardInputMapper::configureParameters() {
-    mParameters.orientationAware = false;
     const PropertyMap& config = getDeviceContext().getConfiguration();
-    config.tryGetProperty("keyboard.orientationAware", mParameters.orientationAware);
-
-    mParameters.handlesKeyRepeat = false;
-    config.tryGetProperty("keyboard.handlesKeyRepeat", mParameters.handlesKeyRepeat);
-
-    mParameters.doNotWakeByDefault = false;
-    config.tryGetProperty("keyboard.doNotWakeByDefault", mParameters.doNotWakeByDefault);
+    mParameters.orientationAware = config.getBool("keyboard.orientationAware").value_or(false);
+    mParameters.handlesKeyRepeat = config.getBool("keyboard.handlesKeyRepeat").value_or(false);
+    mParameters.doNotWakeByDefault = config.getBool("keyboard.doNotWakeByDefault").value_or(false);
 }
 
 void KeyboardInputMapper::dumpParameters(std::string& dump) const {
@@ -295,14 +260,12 @@
         keyMetaState = mMetaState;
     }
 
-    // Key down on external an keyboard should wake the device.
+    // Any key down on an external keyboard should wake the device.
     // We don't do this for internal keyboards to prevent them from waking up in your pocket.
     // For internal keyboards and devices for which the default wake behavior is explicitly
     // prevented (e.g. TV remotes), the key layout file should specify the policy flags for each
     // wake key individually.
-    // TODO: Use the input device configuration to control this behavior more finely.
-    if (down && getDeviceContext().isExternal() && !mParameters.doNotWakeByDefault &&
-        !isMediaKey(keyCode)) {
+    if (down && getDeviceContext().isExternal() && !mParameters.doNotWakeByDefault) {
         policyFlags |= POLICY_FLAG_WAKE;
     }
 
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
index 19a79d7..2ff26ed 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
@@ -20,6 +20,8 @@
 
 #include "RotaryEncoderInputMapper.h"
 
+#include <optional>
+
 #include "CursorScrollAccumulator.h"
 
 namespace android {
@@ -39,18 +41,19 @@
     InputMapper::populateDeviceInfo(info);
 
     if (mRotaryEncoderScrollAccumulator.haveRelativeVWheel()) {
-        float res = 0.0f;
-        if (!getDeviceContext().getConfiguration().tryGetProperty("device.res", res)) {
+        const PropertyMap& config = getDeviceContext().getConfiguration();
+        std::optional<float> res = config.getFloat("device.res");
+        if (!res.has_value()) {
             ALOGW("Rotary Encoder device configuration file didn't specify resolution!\n");
         }
-        if (!getDeviceContext().getConfiguration().tryGetProperty("device.scalingFactor",
-                                                                  mScalingFactor)) {
+        std::optional<float> scalingFactor = config.getFloat("device.scalingFactor");
+        if (!scalingFactor.has_value()) {
             ALOGW("Rotary Encoder device configuration file didn't specify scaling factor,"
                   "default to 1.0!\n");
-            mScalingFactor = 1.0f;
         }
+        mScalingFactor = scalingFactor.value_or(1.0f);
         info->addMotionRange(AMOTION_EVENT_AXIS_SCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f,
-                             res * mScalingFactor);
+                             res.value_or(0.0f) * mScalingFactor);
     }
 }
 
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
index f797bc9..ba08823 100644
--- a/services/inputflinger/reader/mapper/SensorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
@@ -61,12 +61,6 @@
     return AINPUT_SOURCE_SENSOR;
 }
 
-template <typename T>
-bool SensorInputMapper::tryGetProperty(std::string keyName, T& outValue) {
-    const auto& config = getDeviceContext().getConfiguration();
-    return config.tryGetProperty(keyName, outValue);
-}
-
 void SensorInputMapper::parseSensorConfiguration(InputDeviceSensorType sensorType, int32_t absCode,
                                                  int32_t sensorDataIndex, const Axis& axis) {
     auto it = mSensors.find(sensorType);
@@ -201,6 +195,17 @@
 SensorInputMapper::Sensor SensorInputMapper::createSensor(InputDeviceSensorType sensorType,
                                                           const Axis& axis) {
     InputDeviceIdentifier identifier = getDeviceContext().getDeviceIdentifier();
+    const auto& config = getDeviceContext().getConfiguration();
+
+    std::string prefix = "sensor." + ftl::enum_string(sensorType);
+    transform(prefix.begin(), prefix.end(), prefix.begin(), ::tolower);
+
+    int32_t flags = 0;
+    std::optional<int32_t> reportingMode = config.getInt(prefix + ".reportingMode");
+    if (reportingMode.has_value()) {
+        flags |= (*reportingMode & REPORTING_MODE_MASK) << REPORTING_MODE_SHIFT;
+    }
+
     // Sensor Id will be assigned to device Id to distinguish same sensor from multiple input
     // devices, in such a way that the sensor Id will be same as input device Id.
     // The sensorType is to distinguish different sensors within one device.
@@ -209,28 +214,15 @@
                                      identifier.version, sensorType,
                                      InputDeviceSensorAccuracy::ACCURACY_HIGH,
                                      /*maxRange=*/axis.max, /*resolution=*/axis.scale,
-                                     /*power=*/0.0f, /*minDelay=*/0,
-                                     /*fifoReservedEventCount=*/0, /*fifoMaxEventCount=*/0,
-                                     ftl::enum_string(sensorType), /*maxDelay=*/0, /*flags=*/0,
-                                     getDeviceId());
-
-    std::string prefix = "sensor." + ftl::enum_string(sensorType);
-    transform(prefix.begin(), prefix.end(), prefix.begin(), ::tolower);
-
-    int32_t reportingMode = 0;
-    if (!tryGetProperty(prefix + ".reportingMode", reportingMode)) {
-        sensorInfo.flags |= (reportingMode & REPORTING_MODE_MASK) << REPORTING_MODE_SHIFT;
-    }
-
-    tryGetProperty(prefix + ".maxDelay", sensorInfo.maxDelay);
-
-    tryGetProperty(prefix + ".minDelay", sensorInfo.minDelay);
-
-    tryGetProperty(prefix + ".power", sensorInfo.power);
-
-    tryGetProperty(prefix + ".fifoReservedEventCount", sensorInfo.fifoReservedEventCount);
-
-    tryGetProperty(prefix + ".fifoMaxEventCount", sensorInfo.fifoMaxEventCount);
+                                     /*power=*/config.getFloat(prefix + ".power").value_or(0.0f),
+                                     /*minDelay=*/config.getInt(prefix + ".minDelay").value_or(0),
+                                     /*fifoReservedEventCount=*/
+                                     config.getInt(prefix + ".fifoReservedEventCount").value_or(0),
+                                     /*fifoMaxEventCount=*/
+                                     config.getInt(prefix + ".fifoMaxEventCount").value_or(0),
+                                     ftl::enum_string(sensorType),
+                                     /*maxDelay=*/config.getInt(prefix + ".maxDelay").value_or(0),
+                                     /*flags=*/flags, getDeviceId());
 
     return Sensor(sensorInfo);
 }
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.h b/services/inputflinger/reader/mapper/SensorInputMapper.h
index 457567b..043a895 100644
--- a/services/inputflinger/reader/mapper/SensorInputMapper.h
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.h
@@ -16,6 +16,9 @@
 
 #pragma once
 
+#include <optional>
+#include <string>
+
 #include "InputMapper.h"
 
 namespace android {
@@ -120,9 +123,6 @@
 
     [[nodiscard]] std::list<NotifyArgs> sync(nsecs_t when, bool force);
 
-    template <typename T>
-    bool tryGetProperty(std::string keyName, T& outValue);
-
     void parseSensorConfiguration(InputDeviceSensorType sensorType, int32_t absCode,
                                   int32_t sensorDataIndex, const Axis& axis);
 
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 509c2e8..34a4e93 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -366,15 +366,15 @@
             ? Parameters::GestureMode::SINGLE_TOUCH
             : Parameters::GestureMode::MULTI_TOUCH;
 
-    std::string gestureModeString;
-    if (getDeviceContext().getConfiguration().tryGetProperty("touch.gestureMode",
-                                                             gestureModeString)) {
-        if (gestureModeString == "single-touch") {
+    const PropertyMap& config = getDeviceContext().getConfiguration();
+    std::optional<std::string> gestureModeString = config.getString("touch.gestureMode");
+    if (gestureModeString.has_value()) {
+        if (*gestureModeString == "single-touch") {
             mParameters.gestureMode = Parameters::GestureMode::SINGLE_TOUCH;
-        } else if (gestureModeString == "multi-touch") {
+        } else if (*gestureModeString == "multi-touch") {
             mParameters.gestureMode = Parameters::GestureMode::MULTI_TOUCH;
-        } else if (gestureModeString != "default") {
-            ALOGW("Invalid value for touch.gestureMode: '%s'", gestureModeString.c_str());
+        } else if (*gestureModeString != "default") {
+            ALOGW("Invalid value for touch.gestureMode: '%s'", gestureModeString->c_str());
         }
     }
 
@@ -382,24 +382,23 @@
 
     mParameters.hasButtonUnderPad = getDeviceContext().hasInputProperty(INPUT_PROP_BUTTONPAD);
 
-    mParameters.orientationAware = mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN;
-    getDeviceContext().getConfiguration().tryGetProperty("touch.orientationAware",
-                                                         mParameters.orientationAware);
+    mParameters.orientationAware =
+            config.getBool("touch.orientationAware")
+                    .value_or(mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN);
 
     mParameters.orientation = ui::ROTATION_0;
-    std::string orientationString;
-    if (getDeviceContext().getConfiguration().tryGetProperty("touch.orientation",
-                                                             orientationString)) {
+    std::optional<std::string> orientationString = config.getString("touch.orientation");
+    if (orientationString.has_value()) {
         if (mParameters.deviceType != Parameters::DeviceType::TOUCH_SCREEN) {
             ALOGW("The configuration 'touch.orientation' is only supported for touchscreens.");
-        } else if (orientationString == "ORIENTATION_90") {
+        } else if (*orientationString == "ORIENTATION_90") {
             mParameters.orientation = ui::ROTATION_90;
-        } else if (orientationString == "ORIENTATION_180") {
+        } else if (*orientationString == "ORIENTATION_180") {
             mParameters.orientation = ui::ROTATION_180;
-        } else if (orientationString == "ORIENTATION_270") {
+        } else if (*orientationString == "ORIENTATION_270") {
             mParameters.orientation = ui::ROTATION_270;
-        } else if (orientationString != "ORIENTATION_0") {
-            ALOGW("Invalid value for touch.orientation: '%s'", orientationString.c_str());
+        } else if (*orientationString != "ORIENTATION_0") {
+            ALOGW("Invalid value for touch.orientation: '%s'", orientationString->c_str());
         }
     }
 
@@ -413,10 +412,7 @@
         mParameters.hasAssociatedDisplay = true;
         if (mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN) {
             mParameters.associatedDisplayIsExternal = getDeviceContext().isExternal();
-            std::string uniqueDisplayId;
-            getDeviceContext().getConfiguration().tryGetProperty("touch.displayId",
-                                                                 uniqueDisplayId);
-            mParameters.uniqueDisplayId = uniqueDisplayId.c_str();
+            mParameters.uniqueDisplayId = config.getString("touch.displayId").value_or("").c_str();
         }
     }
     if (getDeviceContext().getAssociatedDisplayPort()) {
@@ -426,20 +422,19 @@
     // Initial downs on external touch devices should wake the device.
     // Normally we don't do this for internal touch screens to prevent them from waking
     // up in your pocket but you can enable it using the input device configuration.
-    mParameters.wake = getDeviceContext().isExternal();
-    getDeviceContext().getConfiguration().tryGetProperty("touch.wake", mParameters.wake);
+    mParameters.wake = config.getBool("touch.wake").value_or(getDeviceContext().isExternal());
 
-    InputDeviceUsiVersion usiVersion;
-    if (getDeviceContext().getConfiguration().tryGetProperty("touch.usiVersionMajor",
-                                                             usiVersion.majorVersion) &&
-        getDeviceContext().getConfiguration().tryGetProperty("touch.usiVersionMinor",
-                                                             usiVersion.minorVersion)) {
-        mParameters.usiVersion = usiVersion;
+    std::optional<int32_t> usiVersionMajor = config.getInt("touch.usiVersionMajor");
+    std::optional<int32_t> usiVersionMinor = config.getInt("touch.usiVersionMinor");
+    if (usiVersionMajor.has_value() && usiVersionMinor.has_value()) {
+        mParameters.usiVersion = {
+                .majorVersion = *usiVersionMajor,
+                .minorVersion = *usiVersionMinor,
+        };
     }
 
-    mParameters.enableForInactiveViewport = false;
-    getDeviceContext().getConfiguration().tryGetProperty("touch.enableForInactiveViewport",
-                                                         mParameters.enableForInactiveViewport);
+    mParameters.enableForInactiveViewport =
+            config.getBool("touch.enableForInactiveViewport").value_or(false);
 }
 
 void TouchInputMapper::configureDeviceType() {
@@ -457,7 +452,8 @@
     // Type association takes precedence over the device type found in the idc file.
     std::string deviceTypeString = getDeviceContext().getDeviceTypeAssociation().value_or("");
     if (deviceTypeString.empty()) {
-        getDeviceContext().getConfiguration().tryGetProperty("touch.deviceType", deviceTypeString);
+        deviceTypeString =
+                getDeviceContext().getConfiguration().getString("touch.deviceType").value_or("");
     }
     if (deviceTypeString == "touchScreen") {
         mParameters.deviceType = Parameters::DeviceType::TOUCH_SCREEN;
@@ -805,75 +801,96 @@
         };
     }
 
-    // Compute oriented precision, scales and ranges.
-    // Note that the maximum value reported is an inclusive maximum value so it is one
-    // unit less than the total width or height of the display.
-    // TODO(b/20508709): Calculate the oriented ranges using the input device's raw frame.
-    switch (mInputDeviceOrientation) {
-        case ui::ROTATION_90:
-        case ui::ROTATION_270:
-            mOrientedRanges.x.min = 0;
-            mOrientedRanges.x.max = mDisplayBounds.height - 1;
-            mOrientedRanges.x.flat = 0;
-            mOrientedRanges.x.fuzz = 0;
-            mOrientedRanges.x.resolution = mRawPointerAxes.y.resolution * mRawToDisplay.getScaleY();
+    // Oriented X/Y range (in the rotated display's orientation)
+    const FloatRect rawFrame = Rect{mRawPointerAxes.x.minValue, mRawPointerAxes.y.minValue,
+                                    mRawPointerAxes.x.maxValue, mRawPointerAxes.y.maxValue}
+                                       .toFloatRect();
+    const auto orientedRangeRect = mRawToRotatedDisplay.transform(rawFrame);
+    mOrientedRanges.x.min = orientedRangeRect.left;
+    mOrientedRanges.y.min = orientedRangeRect.top;
+    mOrientedRanges.x.max = orientedRangeRect.right;
+    mOrientedRanges.y.max = orientedRangeRect.bottom;
 
-            mOrientedRanges.y.min = 0;
-            mOrientedRanges.y.max = mDisplayBounds.width - 1;
-            mOrientedRanges.y.flat = 0;
-            mOrientedRanges.y.fuzz = 0;
-            mOrientedRanges.y.resolution = mRawPointerAxes.x.resolution * mRawToDisplay.getScaleX();
-            break;
+    // Oriented flat (in the rotated display's orientation)
+    const auto orientedFlat =
+            transformWithoutTranslation(mRawToRotatedDisplay,
+                                        {static_cast<float>(mRawPointerAxes.x.flat),
+                                         static_cast<float>(mRawPointerAxes.y.flat)});
+    mOrientedRanges.x.flat = std::abs(orientedFlat.x);
+    mOrientedRanges.y.flat = std::abs(orientedFlat.y);
 
-        default:
-            mOrientedRanges.x.min = 0;
-            mOrientedRanges.x.max = mDisplayBounds.width - 1;
-            mOrientedRanges.x.flat = 0;
-            mOrientedRanges.x.fuzz = 0;
-            mOrientedRanges.x.resolution = mRawPointerAxes.x.resolution * mRawToDisplay.getScaleX();
+    // Oriented fuzz (in the rotated display's orientation)
+    const auto orientedFuzz =
+            transformWithoutTranslation(mRawToRotatedDisplay,
+                                        {static_cast<float>(mRawPointerAxes.x.fuzz),
+                                         static_cast<float>(mRawPointerAxes.y.fuzz)});
+    mOrientedRanges.x.fuzz = std::abs(orientedFuzz.x);
+    mOrientedRanges.y.fuzz = std::abs(orientedFuzz.y);
 
-            mOrientedRanges.y.min = 0;
-            mOrientedRanges.y.max = mDisplayBounds.height - 1;
-            mOrientedRanges.y.flat = 0;
-            mOrientedRanges.y.fuzz = 0;
-            mOrientedRanges.y.resolution = mRawPointerAxes.y.resolution * mRawToDisplay.getScaleY();
-            break;
-    }
+    // Oriented resolution (in the rotated display's orientation)
+    const auto orientedRes =
+            transformWithoutTranslation(mRawToRotatedDisplay,
+                                        {static_cast<float>(mRawPointerAxes.x.resolution),
+                                         static_cast<float>(mRawPointerAxes.y.resolution)});
+    mOrientedRanges.x.resolution = std::abs(orientedRes.x);
+    mOrientedRanges.y.resolution = std::abs(orientedRes.y);
 }
 
 void TouchInputMapper::computeInputTransforms() {
-    const ui::Size rawSize{mRawPointerAxes.getRawWidth(), mRawPointerAxes.getRawHeight()};
+    constexpr auto isRotated = [](const ui::Transform::RotationFlags& rotation) {
+        return rotation == ui::Transform::ROT_90 || rotation == ui::Transform::ROT_270;
+    };
 
-    ui::Size rotatedRawSize = rawSize;
-    if (mInputDeviceOrientation == ui::ROTATION_270 || mInputDeviceOrientation == ui::ROTATION_90) {
-        std::swap(rotatedRawSize.width, rotatedRawSize.height);
-    }
-    const auto rotationFlags = ui::Transform::toRotationFlags(-mInputDeviceOrientation);
-    mRawRotation = ui::Transform{rotationFlags};
+    // See notes about input coordinates in the inputflinger docs:
+    // //frameworks/native/services/inputflinger/docs/input_coordinates.md
 
     // Step 1: Undo the raw offset so that the raw coordinate space now starts at (0, 0).
-    ui::Transform undoRawOffset;
-    undoRawOffset.set(-mRawPointerAxes.x.minValue, -mRawPointerAxes.y.minValue);
+    ui::Transform undoOffsetInRaw;
+    undoOffsetInRaw.set(-mRawPointerAxes.x.minValue, -mRawPointerAxes.y.minValue);
 
-    // Step 2: Rotate the raw coordinates to the expected orientation.
-    ui::Transform rotate;
-    // When rotating raw coordinates, the raw size will be used as an offset.
-    // Account for the extra unit added to the raw range when the raw size was calculated.
-    rotate.set(rotationFlags, rotatedRawSize.width - 1, rotatedRawSize.height - 1);
+    // Step 2: Rotate the raw coordinates to account for input device orientation. The coordinates
+    // will now be in the same orientation as the display in ROTATION_0.
+    // Note: Negating an ui::Rotation value will give its inverse rotation.
+    const auto inputDeviceOrientation = ui::Transform::toRotationFlags(-mParameters.orientation);
+    const ui::Size orientedRawSize = isRotated(inputDeviceOrientation)
+            ? ui::Size{mRawPointerAxes.getRawHeight(), mRawPointerAxes.getRawWidth()}
+            : ui::Size{mRawPointerAxes.getRawWidth(), mRawPointerAxes.getRawHeight()};
+    // When rotating raw values, account for the extra unit added when calculating the raw range.
+    const auto orientInRaw = ui::Transform(inputDeviceOrientation, orientedRawSize.width - 1,
+                                           orientedRawSize.height - 1);
 
-    // Step 3: Scale the raw coordinates to the display space.
-    ui::Transform scaleToDisplay;
-    const float xScale = static_cast<float>(mDisplayBounds.width) / rotatedRawSize.width;
-    const float yScale = static_cast<float>(mDisplayBounds.height) / rotatedRawSize.height;
-    scaleToDisplay.set(xScale, 0, 0, yScale);
+    // Step 3: Rotate the raw coordinates to account for the display rotation. The coordinates will
+    // now be in the same orientation as the rotated display. There is no need to rotate the
+    // coordinates to the display rotation if the device is not orientation-aware.
+    const auto viewportRotation = ui::Transform::toRotationFlags(-mViewport.orientation);
+    const auto rotatedRawSize = mParameters.orientationAware && isRotated(viewportRotation)
+            ? ui::Size{orientedRawSize.height, orientedRawSize.width}
+            : orientedRawSize;
+    // When rotating raw values, account for the extra unit added when calculating the raw range.
+    const auto rotateInRaw = mParameters.orientationAware
+            ? ui::Transform(viewportRotation, rotatedRawSize.width - 1, rotatedRawSize.height - 1)
+            : ui::Transform();
 
-    mRawToDisplay = (scaleToDisplay * (rotate * undoRawOffset));
+    // Step 4: Scale the raw coordinates to the display space.
+    // - Here, we assume that the raw surface of the touch device maps perfectly to the surface
+    //   of the display panel. This is usually true for touchscreens.
+    // - From this point onward, we are no longer in the discrete space of the raw coordinates but
+    //   are in the continuous space of the logical display.
+    ui::Transform scaleRawToDisplay;
+    const float xScale = static_cast<float>(mViewport.deviceWidth) / rotatedRawSize.width;
+    const float yScale = static_cast<float>(mViewport.deviceHeight) / rotatedRawSize.height;
+    scaleRawToDisplay.set(xScale, 0, 0, yScale);
 
-    // Calculate the transform that takes raw coordinates to the rotated display space.
-    ui::Transform displayToRotatedDisplay;
-    displayToRotatedDisplay.set(ui::Transform::toRotationFlags(-mViewport.orientation),
-                                mViewport.deviceWidth, mViewport.deviceHeight);
-    mRawToRotatedDisplay = displayToRotatedDisplay * mRawToDisplay;
+    // Step 5: Undo the display rotation to bring us back to the un-rotated display coordinate space
+    // that InputReader uses.
+    const auto undoRotateInDisplay =
+            ui::Transform(viewportRotation, mViewport.deviceWidth, mViewport.deviceHeight)
+                    .inverse();
+
+    // Now put it all together!
+    mRawToRotatedDisplay = (scaleRawToDisplay * (rotateInRaw * (orientInRaw * undoOffsetInRaw)));
+    mRawToDisplay = (undoRotateInDisplay * mRawToRotatedDisplay);
+    mRawRotation = ui::Transform{mRawToDisplay.getOrientation()};
 }
 
 void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) {
@@ -949,6 +966,9 @@
             mPhysicalFrameInRotatedDisplay = {mViewport.physicalLeft, mViewport.physicalTop,
                                               mViewport.physicalRight, mViewport.physicalBottom};
 
+            // TODO(b/257118693): Remove the dependence on the old orientation/rotation logic that
+            //     uses mInputDeviceOrientation. The new logic uses the transforms calculated in
+            //     computeInputTransforms().
             // InputReader works in the un-rotated display coordinate space, so we don't need to do
             // anything if the device is already orientation-aware. If the device is not
             // orientation-aware, then we need to apply the inverse rotation of the display so that
@@ -1136,92 +1156,79 @@
 
     // Size
     out.sizeCalibration = Calibration::SizeCalibration::DEFAULT;
-    std::string sizeCalibrationString;
-    if (in.tryGetProperty("touch.size.calibration", sizeCalibrationString)) {
-        if (sizeCalibrationString == "none") {
+    std::optional<std::string> sizeCalibrationString = in.getString("touch.size.calibration");
+    if (sizeCalibrationString.has_value()) {
+        if (*sizeCalibrationString == "none") {
             out.sizeCalibration = Calibration::SizeCalibration::NONE;
-        } else if (sizeCalibrationString == "geometric") {
+        } else if (*sizeCalibrationString == "geometric") {
             out.sizeCalibration = Calibration::SizeCalibration::GEOMETRIC;
-        } else if (sizeCalibrationString == "diameter") {
+        } else if (*sizeCalibrationString == "diameter") {
             out.sizeCalibration = Calibration::SizeCalibration::DIAMETER;
-        } else if (sizeCalibrationString == "box") {
+        } else if (*sizeCalibrationString == "box") {
             out.sizeCalibration = Calibration::SizeCalibration::BOX;
-        } else if (sizeCalibrationString == "area") {
+        } else if (*sizeCalibrationString == "area") {
             out.sizeCalibration = Calibration::SizeCalibration::AREA;
-        } else if (sizeCalibrationString != "default") {
-            ALOGW("Invalid value for touch.size.calibration: '%s'", sizeCalibrationString.c_str());
+        } else if (*sizeCalibrationString != "default") {
+            ALOGW("Invalid value for touch.size.calibration: '%s'", sizeCalibrationString->c_str());
         }
     }
 
-    float sizeScale;
-
-    if (in.tryGetProperty("touch.size.scale", sizeScale)) {
-        out.sizeScale = sizeScale;
-    }
-    float sizeBias;
-    if (in.tryGetProperty("touch.size.bias", sizeBias)) {
-        out.sizeBias = sizeBias;
-    }
-    bool sizeIsSummed;
-    if (in.tryGetProperty("touch.size.isSummed", sizeIsSummed)) {
-        out.sizeIsSummed = sizeIsSummed;
-    }
+    out.sizeScale = in.getFloat("touch.size.scale");
+    out.sizeBias = in.getFloat("touch.size.bias");
+    out.sizeIsSummed = in.getBool("touch.size.isSummed");
 
     // Pressure
     out.pressureCalibration = Calibration::PressureCalibration::DEFAULT;
-    std::string pressureCalibrationString;
-    if (in.tryGetProperty("touch.pressure.calibration", pressureCalibrationString)) {
-        if (pressureCalibrationString == "none") {
+    std::optional<std::string> pressureCalibrationString =
+            in.getString("touch.pressure.calibration");
+    if (pressureCalibrationString.has_value()) {
+        if (*pressureCalibrationString == "none") {
             out.pressureCalibration = Calibration::PressureCalibration::NONE;
-        } else if (pressureCalibrationString == "physical") {
+        } else if (*pressureCalibrationString == "physical") {
             out.pressureCalibration = Calibration::PressureCalibration::PHYSICAL;
-        } else if (pressureCalibrationString == "amplitude") {
+        } else if (*pressureCalibrationString == "amplitude") {
             out.pressureCalibration = Calibration::PressureCalibration::AMPLITUDE;
-        } else if (pressureCalibrationString != "default") {
+        } else if (*pressureCalibrationString != "default") {
             ALOGW("Invalid value for touch.pressure.calibration: '%s'",
-                  pressureCalibrationString.c_str());
+                  pressureCalibrationString->c_str());
         }
     }
 
-    float pressureScale;
-    if (in.tryGetProperty("touch.pressure.scale", pressureScale)) {
-        out.pressureScale = pressureScale;
-    }
+    out.pressureScale = in.getFloat("touch.pressure.scale");
 
     // Orientation
     out.orientationCalibration = Calibration::OrientationCalibration::DEFAULT;
-    std::string orientationCalibrationString;
-    if (in.tryGetProperty("touch.orientation.calibration", orientationCalibrationString)) {
-        if (orientationCalibrationString == "none") {
+    std::optional<std::string> orientationCalibrationString =
+            in.getString("touch.orientation.calibration");
+    if (orientationCalibrationString.has_value()) {
+        if (*orientationCalibrationString == "none") {
             out.orientationCalibration = Calibration::OrientationCalibration::NONE;
-        } else if (orientationCalibrationString == "interpolated") {
+        } else if (*orientationCalibrationString == "interpolated") {
             out.orientationCalibration = Calibration::OrientationCalibration::INTERPOLATED;
-        } else if (orientationCalibrationString == "vector") {
+        } else if (*orientationCalibrationString == "vector") {
             out.orientationCalibration = Calibration::OrientationCalibration::VECTOR;
-        } else if (orientationCalibrationString != "default") {
+        } else if (*orientationCalibrationString != "default") {
             ALOGW("Invalid value for touch.orientation.calibration: '%s'",
-                  orientationCalibrationString.c_str());
+                  orientationCalibrationString->c_str());
         }
     }
 
     // Distance
     out.distanceCalibration = Calibration::DistanceCalibration::DEFAULT;
-    std::string distanceCalibrationString;
-    if (in.tryGetProperty("touch.distance.calibration", distanceCalibrationString)) {
-        if (distanceCalibrationString == "none") {
+    std::optional<std::string> distanceCalibrationString =
+            in.getString("touch.distance.calibration");
+    if (distanceCalibrationString.has_value()) {
+        if (*distanceCalibrationString == "none") {
             out.distanceCalibration = Calibration::DistanceCalibration::NONE;
-        } else if (distanceCalibrationString == "scaled") {
+        } else if (*distanceCalibrationString == "scaled") {
             out.distanceCalibration = Calibration::DistanceCalibration::SCALED;
-        } else if (distanceCalibrationString != "default") {
+        } else if (*distanceCalibrationString != "default") {
             ALOGW("Invalid value for touch.distance.calibration: '%s'",
-                  distanceCalibrationString.c_str());
+                  distanceCalibrationString->c_str());
         }
     }
 
-    float distanceScale;
-    if (in.tryGetProperty("touch.distance.scale", distanceScale)) {
-        out.distanceScale = distanceScale;
-    }
+    out.distanceScale = in.getFloat("touch.distance.scale");
 }
 
 void TouchInputMapper::resolveCalibration() {
@@ -1651,10 +1658,10 @@
     mPointerController->setPresentation(PointerControllerInterface::Presentation::SPOT);
     mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
 
-    mPointerController->setButtonState(mCurrentRawState.buttonState);
     mPointerController->setSpots(mCurrentCookedState.cookedPointerData.pointerCoords.cbegin(),
                                  mCurrentCookedState.cookedPointerData.idToIndex.cbegin(),
-                                 mCurrentCookedState.cookedPointerData.touchingIdBits,
+                                 mCurrentCookedState.cookedPointerData.touchingIdBits |
+                                         mCurrentCookedState.cookedPointerData.hoveringIdBits,
                                  mViewport.displayId);
 }
 
@@ -2666,8 +2673,7 @@
         // the pointer is hovering again even if the user is not currently touching
         // the touch pad.  This ensures that a view will receive a fresh hover enter
         // event after a tap.
-        float x, y;
-        mPointerController->getPosition(&x, &y);
+        const auto [x, y] = mPointerController->getPosition();
 
         PointerProperties pointerProperties;
         pointerProperties.clear();
@@ -2873,8 +2879,7 @@
             mPointerVelocityControl.reset();
         }
 
-        float x, y;
-        mPointerController->getPosition(&x, &y);
+        const auto [x, y] = mPointerController->getPosition();
 
         mPointerGesture.currentGestureMode = PointerGesture::Mode::BUTTON_CLICK_OR_DRAG;
         mPointerGesture.currentGestureIdBits.clear();
@@ -2900,8 +2905,7 @@
              mPointerGesture.lastGestureMode == PointerGesture::Mode::TAP_DRAG) &&
             lastFingerCount == 1) {
             if (when <= mPointerGesture.tapDownTime + mConfig.pointerGestureTapInterval) {
-                float x, y;
-                mPointerController->getPosition(&x, &y);
+                const auto [x, y] = mPointerController->getPosition();
                 if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop &&
                     fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) {
                     ALOGD_IF(DEBUG_GESTURES, "Gestures: TAP");
@@ -2963,8 +2967,7 @@
         mPointerGesture.currentGestureMode = PointerGesture::Mode::HOVER;
         if (mPointerGesture.lastGestureMode == PointerGesture::Mode::TAP) {
             if (when <= mPointerGesture.tapUpTime + mConfig.pointerGestureTapDragInterval) {
-                float x, y;
-                mPointerController->getPosition(&x, &y);
+                const auto [x, y] = mPointerController->getPosition();
                 if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop &&
                     fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) {
                     mPointerGesture.currentGestureMode = PointerGesture::Mode::TAP_DRAG;
@@ -3000,8 +3003,7 @@
             down = false;
         }
 
-        float x, y;
-        mPointerController->getPosition(&x, &y);
+        const auto [x, y] = mPointerController->getPosition();
 
         mPointerGesture.currentGestureIdBits.clear();
         mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
@@ -3026,8 +3028,6 @@
         prepareMultiFingerPointerGestures(when, outCancelPreviousGesture, outFinishPreviousGesture);
     }
 
-    mPointerController->setButtonState(mCurrentRawState.buttonState);
-
     if (DEBUG_GESTURES) {
         ALOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, "
               "currentGestureMode=%d, currentGestureIdBits=0x%08x, "
@@ -3168,8 +3168,8 @@
         mCurrentRawState.rawPointerData
                 .getCentroidOfTouchingPointers(&mPointerGesture.referenceTouchX,
                                                &mPointerGesture.referenceTouchY);
-        mPointerController->getPosition(&mPointerGesture.referenceGestureX,
-                                        &mPointerGesture.referenceGestureY);
+        std::tie(mPointerGesture.referenceGestureX, mPointerGesture.referenceGestureY) =
+                mPointerController->getPosition();
     }
 
     // Clear the reference deltas for fingers not yet included in the reference calculation.
@@ -3484,8 +3484,7 @@
         hovering = mCurrentCookedState.cookedPointerData.hoveringIdBits.hasBit(id);
         down = !hovering;
 
-        float x, y;
-        mPointerController->getPosition(&x, &y);
+        const auto [x, y] = mPointerController->getPosition();
         mPointerSimple.currentCoords.copyFrom(
                 mCurrentCookedState.cookedPointerData.pointerCoords[index]);
         mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
@@ -3523,9 +3522,8 @@
         down = isPointerDown(mCurrentRawState.buttonState);
         hovering = !down;
 
-        float x, y;
-        mPointerController->getPosition(&x, &y);
-        uint32_t currentIndex = mCurrentRawState.rawPointerData.idToIndex[id];
+        const auto [x, y] = mPointerController->getPosition();
+        const uint32_t currentIndex = mCurrentRawState.rawPointerData.idToIndex[id];
         mPointerSimple.currentCoords.copyFrom(
                 mCurrentCookedState.cookedPointerData.pointerCoords[currentIndex]);
         mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
@@ -3565,15 +3563,13 @@
     if (down || hovering) {
         mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
         mPointerController->clearSpots();
-        mPointerController->setButtonState(mCurrentRawState.buttonState);
         mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
     } else if (!down && !hovering && (mPointerSimple.down || mPointerSimple.hovering)) {
         mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
     }
     int32_t displayId = mPointerController->getDisplayId();
 
-    float xCursorPosition, yCursorPosition;
-    mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
 
     if (mPointerSimple.down && !down) {
         mPointerSimple.down = false;
@@ -3794,7 +3790,7 @@
     float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
     float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
     if (mDeviceMode == DeviceMode::POINTER) {
-        mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+        std::tie(xCursorPosition, yCursorPosition) = mPointerController->getPosition();
     }
     const int32_t deviceId = getDeviceId();
     std::vector<TouchVideoFrame> frames = getDeviceContext().getVideoFrames();
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 7b464ef..df66846 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -16,6 +16,9 @@
 
 #pragma once
 
+#include <optional>
+#include <string>
+
 #include <stdint.h>
 #include <ui/Rotation.h>
 
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index d3af402..3309767 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -20,6 +20,7 @@
 #include <optional>
 
 #include <android/input.h>
+#include <ftl/enum.h>
 #include <input/PrintTools.h>
 #include <linux/input-event-codes.h>
 #include <log/log_main.h>
@@ -216,6 +217,11 @@
 std::list<NotifyArgs> TouchpadInputMapper::configure(nsecs_t when,
                                                      const InputReaderConfiguration* config,
                                                      uint32_t changes) {
+    if (!changes) {
+        // First time configuration
+        mPropertyProvider.loadPropertiesFromIdcFile(getDeviceContext().getConfiguration());
+    }
+
     if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
         std::optional<int32_t> displayId = mPointerController->getDisplayId();
         ui::Rotation orientation = ui::ROTATION_0;
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index d636d44..bc9f4c0 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -98,7 +98,6 @@
         case kGestureTypePinch:
             return handlePinch(when, readTime, gesture);
         default:
-            // TODO(b/251196347): handle more gesture types.
             return {};
     }
 }
@@ -111,8 +110,8 @@
     mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
     mPointerController->move(deltaX, deltaY);
     mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
-    float xCursorPosition, yCursorPosition;
-    mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+
+    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
 
     PointerCoords coords;
     coords.clear();
@@ -136,8 +135,7 @@
     mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
     mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
 
-    float xCursorPosition, yCursorPosition;
-    mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
 
     PointerCoords coords;
     coords.clear();
@@ -202,18 +200,19 @@
                                                      const Gesture& gesture) {
     std::list<NotifyArgs> out;
     PointerCoords& coords = mFakeFingerCoords[0];
-    float xCursorPosition, yCursorPosition;
-    mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
     if (mCurrentClassification != MotionClassification::TWO_FINGER_SWIPE) {
         mCurrentClassification = MotionClassification::TWO_FINGER_SWIPE;
         coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
         coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
         coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
         mDownTime = when;
-        out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
-                                     /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
-                                     mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                     yCursorPosition));
+        NotifyMotionArgs args =
+                makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, /* actionButton= */ 0,
+                               mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
+                               mFakeFingerCoords.data(), xCursorPosition, yCursorPosition);
+        args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
+        out.push_back(args);
     }
     float deltaX = gesture.details.scroll.dx;
     float deltaY = gesture.details.scroll.dy;
@@ -224,9 +223,12 @@
     // TODO(b/262876643): set AXIS_GESTURE_{X,Y}_OFFSET.
     coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, -gesture.details.scroll.dx);
     coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, -gesture.details.scroll.dy);
-    out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
-                                 mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
-                                 mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
+    NotifyMotionArgs args =
+            makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
+                           mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
+                           mFakeFingerCoords.data(), xCursorPosition, yCursorPosition);
+    args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
+    out.push_back(args);
     return out;
 }
 
@@ -239,14 +241,14 @@
         return {};
     }
 
-    float xCursorPosition, yCursorPosition;
-    mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, 0);
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, 0);
-    NotifyArgs args = makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP,
-                                     /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
-                                     mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                     yCursorPosition);
+    NotifyMotionArgs args =
+            makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0,
+                           mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
+                           mFakeFingerCoords.data(), xCursorPosition, yCursorPosition);
+    args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
     mCurrentClassification = MotionClassification::NONE;
     return args;
 }
@@ -256,8 +258,8 @@
                                                                              uint32_t fingerCount,
                                                                              float dx, float dy) {
     std::list<NotifyArgs> out = {};
-    float xCursorPosition, yCursorPosition;
-    mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+
+    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
     if (mCurrentClassification != MotionClassification::MULTI_FINGER_SWIPE) {
         // If the user changes the number of fingers mid-way through a swipe (e.g. they start with
         // three and then put a fourth finger down), the gesture library will treat it as two
@@ -292,8 +294,6 @@
                                          yCursorPosition));
         }
     }
-    // TODO(b/251196347): Set the gesture properties appropriately to avoid needing to negate the Y
-    // values.
     float rotatedDeltaX = dx, rotatedDeltaY = -dy;
     rotateDelta(mOrientation, &rotatedDeltaX, &rotatedDeltaY);
     for (size_t i = 0; i < mSwipeFingerCount; i++) {
@@ -304,8 +304,6 @@
                             coords.getAxisValue(AMOTION_EVENT_AXIS_Y) + rotatedDeltaY);
     }
     float xOffset = dx / (mXAxisInfo.maxValue - mXAxisInfo.minValue);
-    // TODO(b/251196347): Set the gesture properties appropriately to avoid needing to negate the Y
-    // values.
     float yOffset = -dy / (mYAxisInfo.maxValue - mYAxisInfo.minValue);
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, xOffset);
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, yOffset);
@@ -322,8 +320,7 @@
     if (mCurrentClassification != MotionClassification::MULTI_FINGER_SWIPE) {
         return out;
     }
-    float xCursorPosition, yCursorPosition;
-    mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, 0);
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, 0);
 
@@ -347,8 +344,8 @@
 [[nodiscard]] std::list<NotifyArgs> GestureConverter::handlePinch(nsecs_t when, nsecs_t readTime,
                                                                   const Gesture& gesture) {
     std::list<NotifyArgs> out;
-    float xCursorPosition, yCursorPosition;
-    mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+
+    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
 
     // Pinch gesture phases are reported a little differently from others, in that the same details
     // struct is used for all phases of the gesture, just with different zoom_state values. When
diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp
index cd18cd3..be2bfed 100644
--- a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp
+++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp
@@ -19,6 +19,7 @@
 #include "gestures/PropertyProvider.h"
 
 #include <algorithm>
+#include <optional>
 #include <utility>
 
 #include <android-base/stringprintf.h>
@@ -68,11 +69,11 @@
         .free_fn = freeProperty,
 };
 
-bool PropertyProvider::hasProperty(const std::string name) const {
+bool PropertyProvider::hasProperty(const std::string& name) const {
     return mProperties.find(name) != mProperties.end();
 }
 
-GesturesProp& PropertyProvider::getProperty(const std::string name) {
+GesturesProp& PropertyProvider::getProperty(const std::string& name) {
     return mProperties.at(name);
 }
 
@@ -84,7 +85,30 @@
     return dump;
 }
 
-GesturesProp* PropertyProvider::createIntArrayProperty(const std::string name, int* loc,
+void PropertyProvider::loadPropertiesFromIdcFile(const PropertyMap& idcProperties) {
+    // For compatibility with the configuration file syntax, gesture property names in IDC files are
+    // prefixed with "gestureProp." and have spaces replaced by underscores. So, for example, the
+    // configuration key "gestureProp.Palm_Width" refers to the "Palm Width" property.
+    const std::string gesturePropPrefix = "gestureProp.";
+    for (const std::string key : idcProperties.getKeysWithPrefix(gesturePropPrefix)) {
+        std::string propertyName = key.substr(gesturePropPrefix.length());
+        for (size_t i = 0; i < propertyName.length(); i++) {
+            if (propertyName[i] == '_') {
+                propertyName[i] = ' ';
+            }
+        }
+
+        auto it = mProperties.find(propertyName);
+        if (it != mProperties.end()) {
+            it->second.trySetFromIdcProperty(idcProperties, key);
+        } else {
+            ALOGE("Gesture property \"%s\" specified in IDC file does not exist for this device.",
+                  propertyName.c_str());
+        }
+    }
+}
+
+GesturesProp* PropertyProvider::createIntArrayProperty(const std::string& name, int* loc,
                                                        size_t count, const int* init) {
     const auto [it, inserted] =
             mProperties.insert(std::pair{name, GesturesProp(name, loc, count, init)});
@@ -92,7 +116,7 @@
     return &it->second;
 }
 
-GesturesProp* PropertyProvider::createBoolArrayProperty(const std::string name,
+GesturesProp* PropertyProvider::createBoolArrayProperty(const std::string& name,
                                                         GesturesPropBool* loc, size_t count,
                                                         const GesturesPropBool* init) {
     const auto [it, inserted] =
@@ -101,7 +125,7 @@
     return &it->second;
 }
 
-GesturesProp* PropertyProvider::createRealArrayProperty(const std::string name, double* loc,
+GesturesProp* PropertyProvider::createRealArrayProperty(const std::string& name, double* loc,
                                                         size_t count, const double* init) {
     const auto [it, inserted] =
             mProperties.insert(std::pair{name, GesturesProp(name, loc, count, init)});
@@ -109,7 +133,7 @@
     return &it->second;
 }
 
-GesturesProp* PropertyProvider::createStringProperty(const std::string name, const char** loc,
+GesturesProp* PropertyProvider::createStringProperty(const std::string& name, const char** loc,
                                                      const char* const init) {
     const auto [it, inserted] = mProperties.insert(std::pair{name, GesturesProp(name, loc, init)});
     LOG_ALWAYS_FATAL_IF(!inserted, "Gesture property \"%s\" already exists.", name.c_str());
@@ -211,6 +235,57 @@
     setValues(std::get<double*>(mDataPointer), values);
 }
 
+namespace {
+
+// Helper to std::visit with lambdas.
+template <typename... V>
+struct Visitor : V... {};
+// explicit deduction guide (not needed as of C++20)
+template <typename... V>
+Visitor(V...) -> Visitor<V...>;
+
+} // namespace
+
+void GesturesProp::trySetFromIdcProperty(const android::PropertyMap& idcProperties,
+                                         const std::string& propertyName) {
+    if (mCount != 1) {
+        ALOGE("Gesture property \"%s\" is an array, and so cannot be set in an IDC file.",
+              mName.c_str());
+        return;
+    }
+    bool parsedSuccessfully = false;
+    Visitor setVisitor{
+            [&](int*) {
+                if (std::optional<int32_t> value = idcProperties.getInt(propertyName); value) {
+                    parsedSuccessfully = true;
+                    setIntValues({*value});
+                }
+            },
+            [&](GesturesPropBool*) {
+                if (std::optional<bool> value = idcProperties.getBool(propertyName); value) {
+                    parsedSuccessfully = true;
+                    setBoolValues({*value});
+                }
+            },
+            [&](double*) {
+                if (std::optional<double> value = idcProperties.getDouble(propertyName); value) {
+                    parsedSuccessfully = true;
+                    setRealValues({*value});
+                }
+            },
+            [&](const char**) {
+                ALOGE("Gesture property \"%s\" is a string, and so cannot be set in an IDC file.",
+                      mName.c_str());
+                // We've already reported the type mismatch, so set parsedSuccessfully.
+                parsedSuccessfully = true;
+            },
+    };
+    std::visit(setVisitor, mDataPointer);
+
+    ALOGE_IF(!parsedSuccessfully, "Gesture property \"%s\" couldn't be set due to a type mismatch.",
+             mName.c_str());
+}
+
 template <typename T, typename U>
 const std::vector<T> GesturesProp::getValues(U* dataPointer) const {
     if (mGetter != nullptr) {
diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.h b/services/inputflinger/reader/mapper/gestures/PropertyProvider.h
index c21260f..c7e0858 100644
--- a/services/inputflinger/reader/mapper/gestures/PropertyProvider.h
+++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.h
@@ -22,6 +22,7 @@
 #include <vector>
 
 #include "include/gestures.h"
+#include "input/PropertyMap.h"
 
 namespace android {
 
@@ -31,18 +32,20 @@
 // Implementation of a gestures library property provider, which provides configuration parameters.
 class PropertyProvider {
 public:
-    bool hasProperty(const std::string name) const;
-    GesturesProp& getProperty(const std::string name);
+    bool hasProperty(const std::string& name) const;
+    GesturesProp& getProperty(const std::string& name);
     std::string dump() const;
 
+    void loadPropertiesFromIdcFile(const PropertyMap& idcProperties);
+
     // Methods to be called by the gestures library:
-    GesturesProp* createIntArrayProperty(const std::string name, int* loc, size_t count,
+    GesturesProp* createIntArrayProperty(const std::string& name, int* loc, size_t count,
                                          const int* init);
-    GesturesProp* createBoolArrayProperty(const std::string name, GesturesPropBool* loc,
+    GesturesProp* createBoolArrayProperty(const std::string& name, GesturesPropBool* loc,
                                           size_t count, const GesturesPropBool* init);
-    GesturesProp* createRealArrayProperty(const std::string name, double* loc, size_t count,
+    GesturesProp* createRealArrayProperty(const std::string& name, double* loc, size_t count,
                                           const double* init);
-    GesturesProp* createStringProperty(const std::string name, const char** loc,
+    GesturesProp* createStringProperty(const std::string& name, const char** loc,
                                        const char* const init);
 
     void freeProperty(GesturesProp* prop);
@@ -83,6 +86,9 @@
     // Setting string values isn't supported since we don't have a use case yet and the memory
     // management adds additional complexity.
 
+    void trySetFromIdcProperty(const android::PropertyMap& idcProperties,
+                               const std::string& propertyName);
+
 private:
     // Two type parameters are required for these methods, rather than one, due to the gestures
     // library using its own bool type.
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index af40fed..97138c7 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -86,6 +86,13 @@
             ],
         },
     },
+    sanitize: {
+        undefined: true,
+        all_undefined: true,
+        diag: {
+            undefined: true,
+        },
+    },
     static_libs: [
         "libc++fs",
         "libgmock",
diff --git a/services/inputflinger/tests/FakeEventHub.cpp b/services/inputflinger/tests/FakeEventHub.cpp
index 6ac0bfb..ff6d584 100644
--- a/services/inputflinger/tests/FakeEventHub.cpp
+++ b/services/inputflinger/tests/FakeEventHub.cpp
@@ -255,11 +255,12 @@
     return 0;
 }
 
-void FakeEventHub::getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const {
+std::optional<PropertyMap> FakeEventHub::getConfiguration(int32_t deviceId) const {
     Device* device = getDevice(deviceId);
-    if (device) {
-        *outConfiguration = device->configuration;
+    if (device == nullptr) {
+        return {};
     }
+    return device->configuration;
 }
 
 status_t FakeEventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis,
diff --git a/services/inputflinger/tests/FakeEventHub.h b/services/inputflinger/tests/FakeEventHub.h
index 72f8ac0..e0a3f9e 100644
--- a/services/inputflinger/tests/FakeEventHub.h
+++ b/services/inputflinger/tests/FakeEventHub.h
@@ -159,7 +159,7 @@
     ftl::Flags<InputDeviceClass> getDeviceClasses(int32_t deviceId) const override;
     InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const override;
     int32_t getDeviceControllerNumber(int32_t) const override;
-    void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const override;
+    std::optional<PropertyMap> getConfiguration(int32_t deviceId) const override;
     status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
                                  RawAbsoluteAxisInfo* outAxisInfo) const override;
     bool hasRelativeAxis(int32_t deviceId, int axis) const override;
diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp
index 28dad95..ca517f3 100644
--- a/services/inputflinger/tests/FakePointerController.cpp
+++ b/services/inputflinger/tests/FakePointerController.cpp
@@ -37,17 +37,8 @@
     mY = y;
 }
 
-void FakePointerController::setButtonState(int32_t buttonState) {
-    mButtonState = buttonState;
-}
-
-int32_t FakePointerController::getButtonState() const {
-    return mButtonState;
-}
-
-void FakePointerController::getPosition(float* outX, float* outY) const {
-    *outX = mX;
-    *outY = mY;
+FloatPoint FakePointerController::getPosition() const {
+    return {mX, mY};
 }
 
 int32_t FakePointerController::getDisplayId() const {
@@ -59,8 +50,7 @@
 }
 
 void FakePointerController::assertPosition(float x, float y) {
-    float actualX, actualY;
-    getPosition(&actualX, &actualY);
+    const auto [actualX, actualY] = getPosition();
     ASSERT_NEAR(x, actualX, 1);
     ASSERT_NEAR(y, actualY, 1);
 }
@@ -69,13 +59,8 @@
     return mIsPointerShown;
 }
 
-bool FakePointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
-                                      float* outMaxY) const {
-    *outMinX = mMinX;
-    *outMinY = mMinY;
-    *outMaxX = mMaxX;
-    *outMaxY = mMaxY;
-    return mHaveBounds;
+std::optional<FloatRect> FakePointerController::getBounds() const {
+    return mHaveBounds ? std::make_optional<FloatRect>(mMinX, mMinY, mMaxX, mMaxY) : std::nullopt;
 }
 
 void FakePointerController::move(float deltaX, float deltaY) {
diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h
index dd56e65..c374267 100644
--- a/services/inputflinger/tests/FakePointerController.h
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -32,9 +32,7 @@
     const std::map<int32_t, std::vector<int32_t>>& getSpots();
 
     void setPosition(float x, float y) override;
-    void setButtonState(int32_t buttonState) override;
-    int32_t getButtonState() const override;
-    void getPosition(float* outX, float* outY) const override;
+    FloatPoint getPosition() const override;
     int32_t getDisplayId() const override;
     void setDisplayViewport(const DisplayViewport& viewport) override;
 
@@ -42,7 +40,7 @@
     bool isPointerShown();
 
 private:
-    bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override;
+    std::optional<FloatRect> getBounds() const override;
     void move(float deltaX, float deltaY) override;
     void fade(Transition) override;
     void unfade(Transition) override;
@@ -54,7 +52,6 @@
     bool mHaveBounds{false};
     float mMinX{0}, mMinY{0}, mMaxX{0}, mMaxY{0};
     float mX{0}, mY{0};
-    int32_t mButtonState{0};
     int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
     bool mIsPointerShown{false};
 
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index 9c624ba..bbf7e8e 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -252,14 +252,16 @@
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
                       WithGestureScrollDistance(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithDownTime(downTime)));
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithDownTime(downTime),
+                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
                       WithCoords(POINTER_X, POINTER_Y - 10),
                       WithGestureScrollDistance(0, 10, EPSILON),
                       WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER),
+                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE)));
 
     Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
     args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
@@ -269,7 +271,8 @@
                       WithCoords(POINTER_X, POINTER_Y - 15),
                       WithGestureScrollDistance(0, 5, EPSILON),
                       WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER),
+                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE)));
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
                          GESTURES_FLING_START);
@@ -280,7 +283,8 @@
                       WithCoords(POINTER_X, POINTER_Y - 15),
                       WithGestureScrollDistance(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER),
+                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE)));
 }
 
 TEST_F(GestureConverterTest, Scroll_Rotated) {
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index e4ba241..e299643 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -230,10 +230,10 @@
             const auto& motionEvent = static_cast<const MotionEvent&>(event);
             EXPECT_EQ(motionEvent.getEventTime(), args.eventTime);
             EXPECT_EQ(motionEvent.getAction(), args.action);
-            EXPECT_EQ(motionEvent.getX(0), point.x);
-            EXPECT_EQ(motionEvent.getY(0), point.y);
-            EXPECT_EQ(motionEvent.getRawX(0), point.x);
-            EXPECT_EQ(motionEvent.getRawY(0), point.y);
+            EXPECT_NEAR(motionEvent.getX(0), point.x, MotionEvent::ROUNDING_PRECISION);
+            EXPECT_NEAR(motionEvent.getY(0), point.y, MotionEvent::ROUNDING_PRECISION);
+            EXPECT_NEAR(motionEvent.getRawX(0), point.x, MotionEvent::ROUNDING_PRECISION);
+            EXPECT_NEAR(motionEvent.getRawY(0), point.y, MotionEvent::ROUNDING_PRECISION);
         });
     }
 
@@ -3938,8 +3938,7 @@
 public:
     void SetUp() override {
         InputDispatcherTest::SetUp();
-        mDisplayInfos.clear();
-        mWindowInfos.clear();
+        removeAllWindowsAndDisplays();
     }
 
     void addDisplayInfo(int displayId, const ui::Transform& transform) {
@@ -3955,6 +3954,11 @@
         mDispatcher->onWindowInfosChanged(mWindowInfos, mDisplayInfos);
     }
 
+    void removeAllWindowsAndDisplays() {
+        mDisplayInfos.clear();
+        mWindowInfos.clear();
+    }
+
     // Set up a test scenario where the display has a scaled projection and there are two windows
     // on the display.
     std::pair<sp<FakeWindowHandle>, sp<FakeWindowHandle>> setupScaledDisplayScenario() {
@@ -3987,11 +3991,11 @@
     std::vector<gui::WindowInfo> mWindowInfos;
 };
 
-TEST_F(InputDispatcherDisplayProjectionTest, HitTestsInDisplaySpace) {
+TEST_F(InputDispatcherDisplayProjectionTest, HitTestCoordinateSpaceConsistency) {
     auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
     // Send down to the first window. The point is represented in the display space. The point is
-    // selected so that if the hit test was done with the transform applied to it, then it would
-    // end up in the incorrect window.
+    // selected so that if the hit test was performed with the point and the bounds being in
+    // different coordinate spaces, the event would end up in the incorrect window.
     NotifyMotionArgs downMotionArgs =
             generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
                                ADISPLAY_ID_DEFAULT, {PointF{75, 55}});
@@ -4066,6 +4070,81 @@
     EXPECT_EQ(80, event->getY(0));
 }
 
+/** Ensure consistent behavior of InputDispatcher in all orientations. */
+class InputDispatcherDisplayOrientationFixture
+      : public InputDispatcherDisplayProjectionTest,
+        public ::testing::WithParamInterface<ui::Rotation> {};
+
+// This test verifies the touchable region of a window for all rotations of the display by tapping
+// in different locations on the display, specifically points close to the four corners of a
+// window.
+TEST_P(InputDispatcherDisplayOrientationFixture, HitTestInDifferentOrientations) {
+    constexpr static int32_t displayWidth = 400;
+    constexpr static int32_t displayHeight = 800;
+
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    const auto rotation = GetParam();
+
+    // Set up the display with the specified rotation.
+    const bool isRotated = rotation == ui::ROTATION_90 || rotation == ui::ROTATION_270;
+    const int32_t logicalDisplayWidth = isRotated ? displayHeight : displayWidth;
+    const int32_t logicalDisplayHeight = isRotated ? displayWidth : displayHeight;
+    const ui::Transform displayTransform(ui::Transform::toRotationFlags(rotation),
+                                         logicalDisplayWidth, logicalDisplayHeight);
+    addDisplayInfo(ADISPLAY_ID_DEFAULT, displayTransform);
+
+    // Create a window with its bounds determined in the logical display.
+    const Rect frameInLogicalDisplay(100, 100, 200, 300);
+    const Rect frameInDisplay = displayTransform.inverse().transform(frameInLogicalDisplay);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(frameInDisplay, displayTransform);
+    addWindow(window);
+
+    // The following points in logical display space should be inside the window.
+    static const std::array<vec2, 4> insidePoints{
+            {{100, 100}, {199.99, 100}, {100, 299.99}, {199.99, 299.99}}};
+    for (const auto pointInsideWindow : insidePoints) {
+        const vec2 p = displayTransform.inverse().transform(pointInsideWindow);
+        const PointF pointInDisplaySpace{p.x, p.y};
+        const auto down = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                             ADISPLAY_ID_DEFAULT, {pointInDisplaySpace});
+        mDispatcher->notifyMotion(&down);
+        window->consumeMotionDown();
+
+        const auto up = generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                           ADISPLAY_ID_DEFAULT, {pointInDisplaySpace});
+        mDispatcher->notifyMotion(&up);
+        window->consumeMotionUp();
+    }
+
+    // The following points in logical display space should be outside the window.
+    static const std::array<vec2, 5> outsidePoints{
+            {{200, 100}, {100, 300}, {200, 300}, {100, 99.99}, {99.99, 100}}};
+    for (const auto pointOutsideWindow : outsidePoints) {
+        const vec2 p = displayTransform.inverse().transform(pointOutsideWindow);
+        const PointF pointInDisplaySpace{p.x, p.y};
+        const auto down = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                             ADISPLAY_ID_DEFAULT, {pointInDisplaySpace});
+        mDispatcher->notifyMotion(&down);
+
+        const auto up = generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                           ADISPLAY_ID_DEFAULT, {pointInDisplaySpace});
+        mDispatcher->notifyMotion(&up);
+    }
+    window->assertNoEvents();
+}
+
+// Run the precision tests for all rotations.
+INSTANTIATE_TEST_SUITE_P(InputDispatcherDisplayOrientationTests,
+                         InputDispatcherDisplayOrientationFixture,
+                         ::testing::Values(ui::ROTATION_0, ui::ROTATION_90, ui::ROTATION_180,
+                                           ui::ROTATION_270),
+                         [](const testing::TestParamInfo<ui::Rotation>& testParamInfo) {
+                             return ftl::enum_string(testParamInfo.param);
+                         });
+
 using TransferFunction = std::function<bool(const std::unique_ptr<InputDispatcher>& dispatcher,
                                             sp<IBinder>, sp<IBinder>)>;
 
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 1119f73..0de3bfb 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -16,6 +16,7 @@
 
 #include <cinttypes>
 #include <memory>
+#include <optional>
 
 #include <CursorInputMapper.h>
 #include <InputDevice.h>
@@ -1830,7 +1831,7 @@
         ::testing::Types<UinputTouchScreen, UinputExternalStylus, UinputExternalStylusWithPressure>;
 TYPED_TEST_SUITE(StylusButtonIntegrationTest, StylusButtonIntegrationTestTypes);
 
-TYPED_TEST(StylusButtonIntegrationTest, StylusButtonsGenerateKeyEvents) {
+TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsGenerateKeyEvents) {
     const auto stylusId = TestFixture::mStylusInfo.getId();
 
     TestFixture::mStylus->pressKey(BTN_STYLUS);
@@ -1844,7 +1845,7 @@
                   WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
 }
 
-TYPED_TEST(StylusButtonIntegrationTest, StylusButtonsSurroundingTouchGesture) {
+TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsSurroundingTouchGesture) {
     const Point centerPoint = TestFixture::mTouchscreen->getCenterPoint();
     const auto touchscreenId = TestFixture::mTouchscreenInfo.getId();
     const auto stylusId = TestFixture::mStylusInfo.getId();
@@ -1890,7 +1891,7 @@
                   WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
 }
 
-TYPED_TEST(StylusButtonIntegrationTest, StylusButtonsSurroundingHoveringTouchGesture) {
+TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsSurroundingHoveringTouchGesture) {
     const Point centerPoint = TestFixture::mTouchscreen->getCenterPoint();
     const auto touchscreenId = TestFixture::mTouchscreenInfo.getId();
     const auto stylusId = TestFixture::mStylusInfo.getId();
@@ -1966,7 +1967,7 @@
                   WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
 }
 
-TYPED_TEST(StylusButtonIntegrationTest, StylusButtonsWithinTouchGesture) {
+TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsWithinTouchGesture) {
     const Point centerPoint = TestFixture::mTouchscreen->getCenterPoint();
     const auto touchscreenId = TestFixture::mTouchscreenInfo.getId();
     const auto stylusId = TestFixture::mStylusInfo.getId();
@@ -2020,7 +2021,7 @@
                   WithDeviceId(touchscreenId))));
 }
 
-TYPED_TEST(StylusButtonIntegrationTest, StylusButtonMotionEventsDisabled) {
+TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonMotionEventsDisabled) {
     TestFixture::mFakePolicy->setStylusButtonMotionEventsEnabled(false);
     TestFixture::mReader->requestRefreshConfiguration(
             InputReaderConfiguration::CHANGE_STYLUS_BUTTON_REPORTING);
@@ -2077,7 +2078,7 @@
 // ongoing stylus gesture that is being emitted by the touchscreen.
 using ExternalStylusIntegrationTest = TouchIntegrationTest;
 
-TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureReported) {
+TEST_F(ExternalStylusIntegrationTest, DISABLED_FusedExternalStylusPressureReported) {
     const Point centerPoint = mDevice->getCenterPoint();
 
     // Create an external stylus capable of reporting pressure data that
@@ -2123,7 +2124,7 @@
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasNotCalled());
 }
 
-TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureNotReported) {
+TEST_F(ExternalStylusIntegrationTest, DISABLED_FusedExternalStylusPressureNotReported) {
     const Point centerPoint = mDevice->getCenterPoint();
 
     // Create an external stylus capable of reporting pressure data that
@@ -2191,7 +2192,7 @@
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasNotCalled());
 }
 
-TEST_F(ExternalStylusIntegrationTest, UnfusedExternalStylus) {
+TEST_F(ExternalStylusIntegrationTest, DISABLED_UnfusedExternalStylus) {
     const Point centerPoint = mDevice->getCenterPoint();
 
     // Create an external stylus device that does not support pressure. It should not affect any
@@ -2355,10 +2356,10 @@
     InputReaderConfiguration config;
     std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, &config, 0);
 
-    std::string propertyValue;
-    ASSERT_TRUE(mDevice->getConfiguration().tryGetProperty("key", propertyValue))
+    std::optional<std::string> propertyValue = mDevice->getConfiguration().getString("key");
+    ASSERT_TRUE(propertyValue.has_value())
             << "Device should have read configuration during configuration phase.";
-    ASSERT_EQ("value", propertyValue);
+    ASSERT_EQ("value", *propertyValue);
 
     ASSERT_NO_FATAL_FAILURE(mapper1.assertConfigureWasCalled());
     ASSERT_NO_FATAL_FAILURE(mapper2.assertConfigureWasCalled());
@@ -3658,8 +3659,8 @@
 };
 
 TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior) {
-    // For external devices, non-media keys will trigger wake on key down. Media keys need to be
-    // marked as WAKE in the keylayout file to trigger wake.
+    // For external devices, keys will trigger wake on key down. Media keys should also trigger
+    // wake if triggered from external devices.
 
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, 0);
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_PLAY, 0, AKEYCODE_MEDIA_PLAY, 0);
@@ -3681,7 +3682,7 @@
 
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAY, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(uint32_t(0), args.policyFlags);
+    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
 
     process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAY, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
@@ -4113,7 +4114,6 @@
 
     mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
     mFakePointerController->setPosition(100, 200);
-    mFakePointerController->setButtonState(0);
 
     NotifyMotionArgs motionArgs;
     NotifyKeyArgs keyArgs;
@@ -4124,14 +4124,12 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
 
@@ -4140,21 +4138,18 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(0, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(0, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(0, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
@@ -4165,26 +4160,20 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
-            motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
-            mFakePointerController->getButtonState());
+              motionArgs.buttonState);
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
-            mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
-            motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
-            mFakePointerController->getButtonState());
+              motionArgs.buttonState);
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
 
@@ -4193,14 +4182,12 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
 
@@ -4209,7 +4196,6 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(0, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0);
@@ -4217,14 +4203,12 @@
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(0, mFakePointerController->getButtonState());
     ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(0, mFakePointerController->getButtonState());
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
@@ -4239,14 +4223,12 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
@@ -4255,14 +4237,12 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(0, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(0, mFakePointerController->getButtonState());
 
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
@@ -4280,14 +4260,12 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
@@ -4296,14 +4274,12 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(0, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(0, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
@@ -4321,14 +4297,12 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
@@ -4337,14 +4311,12 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(0, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(0, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
@@ -4362,14 +4334,12 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
@@ -4378,14 +4348,12 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(0, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(0, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
@@ -4400,7 +4368,6 @@
 
     mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
     mFakePointerController->setPosition(100, 200);
-    mFakePointerController->setButtonState(0);
 
     NotifyMotionArgs args;
 
@@ -4427,7 +4394,6 @@
 
     mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
     mFakePointerController->setPosition(100, 200);
-    mFakePointerController->setButtonState(0);
 
     NotifyMotionArgs args;
 
@@ -4606,7 +4572,6 @@
 
     mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
     mFakePointerController->setPosition(100, 200);
-    mFakePointerController->setButtonState(0);
 
     // Ensure input events are generated for the secondary display.
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
@@ -4634,7 +4599,6 @@
 
     mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
     mFakePointerController->setPosition(100, 200);
-    mFakePointerController->setButtonState(0);
 
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
@@ -6754,28 +6718,30 @@
     // The values inside DisplayViewport are expected to be pre-rotated. This updates the current
     // DisplayViewport to pre-rotate the values. The viewport's physical display will be set to the
     // rotated equivalent of the given un-rotated physical display bounds.
-    void configurePhysicalDisplay(ui::Rotation orientation, Rect naturalPhysicalDisplay) {
+    void configurePhysicalDisplay(ui::Rotation orientation, Rect naturalPhysicalDisplay,
+                                  int32_t naturalDisplayWidth = DISPLAY_WIDTH,
+                                  int32_t naturalDisplayHeight = DISPLAY_HEIGHT) {
         uint32_t inverseRotationFlags;
-        auto width = DISPLAY_WIDTH;
-        auto height = DISPLAY_HEIGHT;
+        auto rotatedWidth = naturalDisplayWidth;
+        auto rotatedHeight = naturalDisplayHeight;
         switch (orientation) {
             case ui::ROTATION_90:
                 inverseRotationFlags = ui::Transform::ROT_270;
-                std::swap(width, height);
+                std::swap(rotatedWidth, rotatedHeight);
                 break;
             case ui::ROTATION_180:
                 inverseRotationFlags = ui::Transform::ROT_180;
                 break;
             case ui::ROTATION_270:
                 inverseRotationFlags = ui::Transform::ROT_90;
-                std::swap(width, height);
+                std::swap(rotatedWidth, rotatedHeight);
                 break;
             case ui::ROTATION_0:
                 inverseRotationFlags = ui::Transform::ROT_0;
                 break;
         }
 
-        const ui::Transform rotation(inverseRotationFlags, width, height);
+        const ui::Transform rotation(inverseRotationFlags, rotatedWidth, rotatedHeight);
         const Rect rotatedPhysicalDisplay = rotation.transform(naturalPhysicalDisplay);
 
         std::optional<DisplayViewport> internalViewport =
@@ -6794,8 +6760,8 @@
         v.physicalRight = rotatedPhysicalDisplay.right;
         v.physicalBottom = rotatedPhysicalDisplay.bottom;
 
-        v.deviceWidth = width;
-        v.deviceHeight = height;
+        v.deviceWidth = rotatedWidth;
+        v.deviceHeight = rotatedHeight;
 
         v.isActive = true;
         v.uniqueId = UNIQUE_ID;
@@ -6909,6 +6875,243 @@
     }
 }
 
+// --- TouchscreenPrecisionTests ---
+
+// This test suite is used to ensure that touchscreen devices are scaled and configured correctly
+// in various orientations and with different display rotations. We configure the touchscreen to
+// have a higher resolution than that of the display by an integer scale factor in each axis so that
+// we can enforce that coordinates match precisely as expected.
+class TouchscreenPrecisionTestsFixture : public TouchDisplayProjectionTest,
+                                         public ::testing::WithParamInterface<ui::Rotation> {
+public:
+    void SetUp() override {
+        SingleTouchInputMapperTest::SetUp();
+
+        // Prepare the raw axes to have twice the resolution of the display in the X axis and
+        // four times the resolution of the display in the Y axis.
+        prepareButtons();
+        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_X, PRECISION_RAW_X_MIN, PRECISION_RAW_X_MAX,
+                                       PRECISION_RAW_X_FLAT, PRECISION_RAW_X_FUZZ,
+                                       PRECISION_RAW_X_RES);
+        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_Y, PRECISION_RAW_Y_MIN, PRECISION_RAW_Y_MAX,
+                                       PRECISION_RAW_Y_FLAT, PRECISION_RAW_Y_FUZZ,
+                                       PRECISION_RAW_Y_RES);
+    }
+
+    static const int32_t PRECISION_RAW_X_MIN = TouchInputMapperTest::RAW_X_MIN;
+    static const int32_t PRECISION_RAW_X_MAX = PRECISION_RAW_X_MIN + DISPLAY_WIDTH * 2 - 1;
+    static const int32_t PRECISION_RAW_Y_MIN = TouchInputMapperTest::RAW_Y_MIN;
+    static const int32_t PRECISION_RAW_Y_MAX = PRECISION_RAW_Y_MIN + DISPLAY_HEIGHT * 4 - 1;
+
+    static const int32_t PRECISION_RAW_X_RES = 50;  // units per millimeter
+    static const int32_t PRECISION_RAW_Y_RES = 100; // units per millimeter
+
+    static const int32_t PRECISION_RAW_X_FLAT = 16;
+    static const int32_t PRECISION_RAW_Y_FLAT = 32;
+
+    static const int32_t PRECISION_RAW_X_FUZZ = 4;
+    static const int32_t PRECISION_RAW_Y_FUZZ = 8;
+
+    static const std::array<Point, 4> kRawCorners;
+};
+
+const std::array<Point, 4> TouchscreenPrecisionTestsFixture::kRawCorners = {{
+        {PRECISION_RAW_X_MIN, PRECISION_RAW_Y_MIN}, // left-top
+        {PRECISION_RAW_X_MAX, PRECISION_RAW_Y_MIN}, // right-top
+        {PRECISION_RAW_X_MAX, PRECISION_RAW_Y_MAX}, // right-bottom
+        {PRECISION_RAW_X_MIN, PRECISION_RAW_Y_MAX}, // left-bottom
+}};
+
+// Tests for how the touchscreen is oriented relative to the natural orientation of the display.
+// For example, if a touchscreen is configured with an orientation of 90 degrees, it is a portrait
+// touchscreen panel that is used on a device whose natural display orientation is in landscape.
+TEST_P(TouchscreenPrecisionTestsFixture, OrientationPrecision) {
+    enum class Orientation {
+        ORIENTATION_0 = ui::toRotationInt(ui::ROTATION_0),
+        ORIENTATION_90 = ui::toRotationInt(ui::ROTATION_90),
+        ORIENTATION_180 = ui::toRotationInt(ui::ROTATION_180),
+        ORIENTATION_270 = ui::toRotationInt(ui::ROTATION_270),
+        ftl_last = ORIENTATION_270,
+    };
+    using Orientation::ORIENTATION_0, Orientation::ORIENTATION_90, Orientation::ORIENTATION_180,
+            Orientation::ORIENTATION_270;
+    static const std::map<Orientation, std::array<vec2, 4> /*mappedCorners*/> kMappedCorners = {
+            {ORIENTATION_0, {{{0, 0}, {479.5, 0}, {479.5, 799.75}, {0, 799.75}}}},
+            {ORIENTATION_90, {{{0, 479.5}, {0, 0}, {799.75, 0}, {799.75, 479.5}}}},
+            {ORIENTATION_180, {{{479.5, 799.75}, {0, 799.75}, {0, 0}, {479.5, 0}}}},
+            {ORIENTATION_270, {{{799.75, 0}, {799.75, 479.5}, {0, 479.5}, {0, 0}}}},
+    };
+
+    const auto touchscreenOrientation = static_cast<Orientation>(ui::toRotationInt(GetParam()));
+
+    // Configure the touchscreen as being installed in the one of the four different orientations
+    // relative to the display.
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    addConfigurationProperty("touch.orientation", ftl::enum_string(touchscreenOrientation).c_str());
+    prepareDisplay(ui::ROTATION_0);
+
+    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+
+    // If the touchscreen is installed in a rotated orientation relative to the display (i.e. in
+    // orientations of either 90 or 270) this means the display's natural resolution will be
+    // flipped.
+    const bool displayRotated =
+            touchscreenOrientation == ORIENTATION_90 || touchscreenOrientation == ORIENTATION_270;
+    const int32_t width = displayRotated ? DISPLAY_HEIGHT : DISPLAY_WIDTH;
+    const int32_t height = displayRotated ? DISPLAY_WIDTH : DISPLAY_HEIGHT;
+    const Rect physicalFrame{0, 0, width, height};
+    configurePhysicalDisplay(ui::ROTATION_0, physicalFrame, width, height);
+
+    const auto& expectedPoints = kMappedCorners.at(touchscreenOrientation);
+    const float expectedPrecisionX = displayRotated ? 4 : 2;
+    const float expectedPrecisionY = displayRotated ? 2 : 4;
+
+    // Test all four corners.
+    for (int i = 0; i < 4; i++) {
+        const auto& raw = kRawCorners[i];
+        processDown(mapper, raw.x, raw.y);
+        processSync(mapper);
+        const auto& expected = expectedPoints[i];
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                      WithCoords(expected.x, expected.y),
+                      WithPrecision(expectedPrecisionX, expectedPrecisionY))))
+                << "Failed to process raw point (" << raw.x << ", " << raw.y << ") "
+                << "with touchscreen orientation "
+                << ftl::enum_string(touchscreenOrientation).c_str() << ", expected point ("
+                << expected.x << ", " << expected.y << ").";
+        processUp(mapper);
+        processSync(mapper);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                      WithCoords(expected.x, expected.y))));
+    }
+}
+
+TEST_P(TouchscreenPrecisionTestsFixture, RotationPrecisionWhenOrientationAware) {
+    static const std::map<ui::Rotation /*rotation*/, std::array<vec2, 4> /*mappedCorners*/>
+            kMappedCorners = {
+                    {ui::ROTATION_0, {{{0, 0}, {479.5, 0}, {479.5, 799.75}, {0, 799.75}}}},
+                    {ui::ROTATION_90, {{{0.5, 0}, {480, 0}, {480, 799.75}, {0.5, 799.75}}}},
+                    {ui::ROTATION_180, {{{0.5, 0.25}, {480, 0.25}, {480, 800}, {0.5, 800}}}},
+                    {ui::ROTATION_270, {{{0, 0.25}, {479.5, 0.25}, {479.5, 800}, {0, 800}}}},
+            };
+
+    const ui::Rotation displayRotation = GetParam();
+
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(displayRotation);
+
+    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+
+    const auto& expectedPoints = kMappedCorners.at(displayRotation);
+
+    // Test all four corners.
+    for (int i = 0; i < 4; i++) {
+        const auto& expected = expectedPoints[i];
+        const auto& raw = kRawCorners[i];
+        processDown(mapper, raw.x, raw.y);
+        processSync(mapper);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                      WithCoords(expected.x, expected.y), WithPrecision(2, 4))))
+                << "Failed to process raw point (" << raw.x << ", " << raw.y << ") "
+                << "with display rotation " << ui::toCString(displayRotation)
+                << ", expected point (" << expected.x << ", " << expected.y << ").";
+        processUp(mapper);
+        processSync(mapper);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                      WithCoords(expected.x, expected.y))));
+    }
+}
+
+TEST_P(TouchscreenPrecisionTestsFixture, RotationPrecisionOrientationAwareInOri270) {
+    static const std::map<ui::Rotation /*orientation*/, std::array<vec2, 4> /*mappedCorners*/>
+            kMappedCorners = {
+                    {ui::ROTATION_0, {{{799.75, 0}, {799.75, 479.5}, {0, 479.5}, {0, 0}}}},
+                    {ui::ROTATION_90, {{{800, 0}, {800, 479.5}, {0.25, 479.5}, {0.25, 0}}}},
+                    {ui::ROTATION_180, {{{800, 0.5}, {800, 480}, {0.25, 480}, {0.25, 0.5}}}},
+                    {ui::ROTATION_270, {{{799.75, 0.5}, {799.75, 480}, {0, 480}, {0, 0.5}}}},
+            };
+
+    const ui::Rotation displayRotation = GetParam();
+
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    addConfigurationProperty("touch.orientation", "ORIENTATION_270");
+
+    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+
+    // Ori 270, so width and height swapped
+    const Rect physicalFrame{0, 0, DISPLAY_HEIGHT, DISPLAY_WIDTH};
+    prepareDisplay(displayRotation);
+    configurePhysicalDisplay(displayRotation, physicalFrame, DISPLAY_HEIGHT, DISPLAY_WIDTH);
+
+    const auto& expectedPoints = kMappedCorners.at(displayRotation);
+
+    // Test all four corners.
+    for (int i = 0; i < 4; i++) {
+        const auto& expected = expectedPoints[i];
+        const auto& raw = kRawCorners[i];
+        processDown(mapper, raw.x, raw.y);
+        processSync(mapper);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                      WithCoords(expected.x, expected.y), WithPrecision(4, 2))))
+                << "Failed to process raw point (" << raw.x << ", " << raw.y << ") "
+                << "with display rotation " << ui::toCString(displayRotation)
+                << ", expected point (" << expected.x << ", " << expected.y << ").";
+        processUp(mapper);
+        processSync(mapper);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                      WithCoords(expected.x, expected.y))));
+    }
+}
+
+TEST_P(TouchscreenPrecisionTestsFixture, MotionRangesAreOrientedInRotatedDisplay) {
+    const ui::Rotation displayRotation = GetParam();
+
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(displayRotation);
+
+    __attribute__((unused)) SingleTouchInputMapper& mapper =
+            addMapperAndConfigure<SingleTouchInputMapper>();
+
+    const InputDeviceInfo deviceInfo = mDevice->getDeviceInfo();
+    // MotionRanges use display pixels as their units
+    const auto* xRange = deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHSCREEN);
+    const auto* yRange = deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHSCREEN);
+
+    // The MotionRanges should be oriented in the rotated display's coordinate space
+    const bool displayRotated =
+            displayRotation == ui::ROTATION_90 || displayRotation == ui::ROTATION_270;
+
+    constexpr float MAX_X = 479.5;
+    constexpr float MAX_Y = 799.75;
+    EXPECT_EQ(xRange->min, 0.f);
+    EXPECT_EQ(yRange->min, 0.f);
+    EXPECT_EQ(xRange->max, displayRotated ? MAX_Y : MAX_X);
+    EXPECT_EQ(yRange->max, displayRotated ? MAX_X : MAX_Y);
+
+    EXPECT_EQ(xRange->flat, 8.f);
+    EXPECT_EQ(yRange->flat, 8.f);
+
+    EXPECT_EQ(xRange->fuzz, 2.f);
+    EXPECT_EQ(yRange->fuzz, 2.f);
+
+    EXPECT_EQ(xRange->resolution, 25.f); // pixels per millimeter
+    EXPECT_EQ(yRange->resolution, 25.f); // pixels per millimeter
+}
+
+// Run the precision tests for all rotations.
+INSTANTIATE_TEST_SUITE_P(TouchscreenPrecisionTests, TouchscreenPrecisionTestsFixture,
+                         ::testing::Values(ui::ROTATION_0, ui::ROTATION_90, ui::ROTATION_180,
+                                           ui::ROTATION_270),
+                         [](const testing::TestParamInfo<ui::Rotation>& testParamInfo) {
+                             return ftl::enum_string(testParamInfo.param);
+                         });
+
 // --- ExternalStylusFusionTest ---
 
 class ExternalStylusFusionTest : public SingleTouchInputMapperTest {
@@ -8924,7 +9127,6 @@
             std::make_shared<FakePointerController>();
     fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
     fakePointerController->setPosition(100, 200);
-    fakePointerController->setButtonState(0);
     mFakePolicy->setPointerController(fakePointerController);
 
     mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
@@ -9919,7 +10121,6 @@
             std::make_shared<FakePointerController>();
     fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
     fakePointerController->setPosition(0, 0);
-    fakePointerController->setButtonState(0);
 
     // prepare device and capture
     prepareDisplay(ui::ROTATION_0);
@@ -10069,7 +10270,6 @@
             std::make_shared<FakePointerController>();
     fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
     fakePointerController->setPosition(0, 0);
-    fakePointerController->setButtonState(0);
 
     // prepare device and capture
     prepareDisplay(ui::ROTATION_0);
@@ -10209,7 +10409,6 @@
                 std::make_shared<FakePointerController>();
         fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
         fakePointerController->setPosition(0, 0);
-        fakePointerController->setButtonState(0);
         prepareDisplay(ui::ROTATION_0);
 
         prepareAxes(POSITION);
diff --git a/services/inputflinger/tests/PropertyProvider_test.cpp b/services/inputflinger/tests/PropertyProvider_test.cpp
index 42a6a9f..8a40e78 100644
--- a/services/inputflinger/tests/PropertyProvider_test.cpp
+++ b/services/inputflinger/tests/PropertyProvider_test.cpp
@@ -18,6 +18,7 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include "TestConstants.h"
 #include "include/gestures.h"
 
 namespace android {
@@ -283,4 +284,68 @@
     EXPECT_FALSE(mProvider.hasProperty("Foo"));
 }
 
+class PropertyProviderIdcLoadingTest : public testing::Test {
+protected:
+    void SetUp() override {
+        int initialInt = 0;
+        GesturesPropBool initialBool = false;
+        double initialReal = 0.0;
+        gesturePropProvider.create_int_fn(&mProvider, "An Integer", &mIntData, 1, &initialInt);
+        gesturePropProvider.create_bool_fn(&mProvider, "A Boolean", &mBoolData, 1, &initialBool);
+        gesturePropProvider.create_real_fn(&mProvider, "A Real", &mRealData, 1, &initialReal);
+    }
+
+    PropertyProvider mProvider;
+
+    int mIntData;
+    GesturesPropBool mBoolData;
+    double mRealData;
+};
+
+TEST_F(PropertyProviderIdcLoadingTest, AllCorrect) {
+    PropertyMap idcProps;
+    idcProps.addProperty("gestureProp.An_Integer", "42");
+    idcProps.addProperty("gestureProp.A_Boolean", "1");
+    idcProps.addProperty("gestureProp.A_Real", "3.14159");
+
+    mProvider.loadPropertiesFromIdcFile(idcProps);
+    EXPECT_THAT(mProvider.getProperty("An Integer").getIntValues(), ElementsAre(42));
+    EXPECT_THAT(mProvider.getProperty("A Boolean").getBoolValues(), ElementsAre(true));
+    EXPECT_NEAR(mProvider.getProperty("A Real").getRealValues()[0], 3.14159, EPSILON);
+}
+
+TEST_F(PropertyProviderIdcLoadingTest, InvalidPropsIgnored) {
+    int intArrayData[2];
+    int initialInts[2] = {0, 1};
+    gesturePropProvider.create_int_fn(&mProvider, "Two Integers", intArrayData, 2, initialInts);
+
+    PropertyMap idcProps;
+    // Wrong type
+    idcProps.addProperty("gestureProp.An_Integer", "37.25");
+    // Wrong size
+    idcProps.addProperty("gestureProp.Two_Integers", "42");
+    // Doesn't exist
+    idcProps.addProperty("gestureProp.Some_Nonexistent_Property", "1");
+    // A valid assignment that should still be applied despite the others being invalid
+    idcProps.addProperty("gestureProp.A_Real", "3.14159");
+
+    mProvider.loadPropertiesFromIdcFile(idcProps);
+    EXPECT_THAT(mProvider.getProperty("An Integer").getIntValues(), ElementsAre(0));
+    EXPECT_THAT(mProvider.getProperty("Two Integers").getIntValues(), ElementsAre(0, 1));
+    EXPECT_FALSE(mProvider.hasProperty("Some Nonexistent Property"));
+    EXPECT_NEAR(mProvider.getProperty("A Real").getRealValues()[0], 3.14159, EPSILON);
+}
+
+TEST_F(PropertyProviderIdcLoadingTest, FunkyName) {
+    int data;
+    int initialData = 0;
+    gesturePropProvider.create_int_fn(&mProvider, "  I lOvE sNAKes ", &data, 1, &initialData);
+
+    PropertyMap idcProps;
+    idcProps.addProperty("gestureProp.__I_lOvE_sNAKes_", "42");
+
+    mProvider.loadPropertiesFromIdcFile(idcProps);
+    EXPECT_THAT(mProvider.getProperty("  I lOvE sNAKes ").getIntValues(), ElementsAre(42));
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/TestConstants.h b/services/inputflinger/tests/TestConstants.h
index 27881f6..ad48b0f 100644
--- a/services/inputflinger/tests/TestConstants.h
+++ b/services/inputflinger/tests/TestConstants.h
@@ -16,6 +16,10 @@
 
 #pragma once
 
+#include <chrono>
+
+#include <utils/Timers.h>
+
 namespace android {
 
 using std::chrono_literals::operator""ms;
diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h
index b9d9607..09f7ae8 100644
--- a/services/inputflinger/tests/TestInputListenerMatchers.h
+++ b/services/inputflinger/tests/TestInputListenerMatchers.h
@@ -145,7 +145,7 @@
 
 MATCHER_P(WithFlags, flags, "InputEvent with specified flags") {
     *result_listener << "expected flags " << flags << ", but got " << arg.flags;
-    return arg.flags == flags;
+    return arg.flags == static_cast<int32_t>(flags);
 }
 
 MATCHER_P(WithMotionClassification, classification,
@@ -176,4 +176,10 @@
     return arg.downTime == downTime;
 }
 
+MATCHER_P2(WithPrecision, xPrecision, yPrecision, "MotionEvent with specified precision") {
+    *result_listener << "expected x-precision " << xPrecision << " and y-precision " << yPrecision
+                     << ", but got " << arg.xPrecision << " and " << arg.yPrecision;
+    return arg.xPrecision == xPrecision && arg.yPrecision == yPrecision;
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index 7c9be5c..2cb5cdf 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -86,8 +86,8 @@
     int32_t getDeviceControllerNumber(int32_t deviceId) const override {
         return mFdp->ConsumeIntegral<int32_t>();
     }
-    void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const override {
-        *outConfiguration = mFuzzConfig;
+    std::optional<PropertyMap> getConfiguration(int32_t deviceId) const override {
+        return mFuzzConfig;
     }
     status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
                                  RawAbsoluteAxisInfo* outAxisInfo) const override {
@@ -225,14 +225,21 @@
 public:
     FuzzPointerController(std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp) : mFdp(mFdp) {}
     ~FuzzPointerController() {}
-    bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override {
-        return mFdp->ConsumeBool();
+    std::optional<FloatRect> getBounds() const override {
+        if (mFdp->ConsumeBool()) {
+            return {};
+        } else {
+            return FloatRect{mFdp->ConsumeFloatingPoint<float>(),
+                             mFdp->ConsumeFloatingPoint<float>(),
+                             mFdp->ConsumeFloatingPoint<float>(),
+                             mFdp->ConsumeFloatingPoint<float>()};
+        }
     }
     void move(float deltaX, float deltaY) override {}
-    void setButtonState(int32_t buttonState) override {}
-    int32_t getButtonState() const override { return mFdp->ConsumeIntegral<int32_t>(); }
     void setPosition(float x, float y) override {}
-    void getPosition(float* outX, float* outY) const override {}
+    FloatPoint getPosition() const override {
+        return {mFdp->ConsumeFloatingPoint<float>(), mFdp->ConsumeFloatingPoint<float>()};
+    }
     void fade(Transition transition) override {}
     void unfade(Transition transition) override {}
     void setPresentation(Presentation presentation) override {}
diff --git a/services/memtrackproxy/MemtrackProxy.cpp b/services/memtrackproxy/MemtrackProxy.cpp
index 4676167..9e41a93 100644
--- a/services/memtrackproxy/MemtrackProxy.cpp
+++ b/services/memtrackproxy/MemtrackProxy.cpp
@@ -97,9 +97,14 @@
     return calling_pid == request_pid;
 }
 
-MemtrackProxy::MemtrackProxy()
-      : memtrack_hidl_instance_(MemtrackProxy::MemtrackHidlInstance()),
-        memtrack_aidl_instance_(MemtrackProxy::MemtrackAidlInstance()) {}
+MemtrackProxy::MemtrackProxy() {
+    memtrack_aidl_instance_ = MemtrackProxy::MemtrackAidlInstance();
+
+    // Only check for a HIDL implementation if we failed to get the AIDL service
+    if (!memtrack_aidl_instance_) {
+        memtrack_hidl_instance_ = MemtrackProxy::MemtrackHidlInstance();
+    }
+}
 
 ndk::ScopedAStatus MemtrackProxy::getMemory(int pid, MemtrackType type,
                                             std::vector<MemtrackRecord>* _aidl_return) {
diff --git a/services/sensorservice/aidl/fuzzer/fuzzer.cpp b/services/sensorservice/aidl/fuzzer/fuzzer.cpp
index 1b63d76..ee8ceb3 100644
--- a/services/sensorservice/aidl/fuzzer/fuzzer.cpp
+++ b/services/sensorservice/aidl/fuzzer/fuzzer.cpp
@@ -16,7 +16,7 @@
 #include <fuzzbinder/libbinder_ndk_driver.h>
 #include <fuzzer/FuzzedDataProvider.h>
 
-#include <ServiceManager.h>
+#include <fakeservicemanager/FakeServiceManager.h>
 #include <android-base/logging.h>
 #include <android/binder_interface_utils.h>
 #include <fuzzbinder/random_binder.h>
@@ -29,7 +29,7 @@
 [[clang::no_destroy]] static std::once_flag gSmOnce;
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    static android::sp<android::ServiceManager> fakeServiceManager = new android::ServiceManager();
+    static android::sp<android::FakeServiceManager> fakeServiceManager = new android::FakeServiceManager();
     std::call_once(gSmOnce, [&] { setDefaultServiceManager(fakeServiceManager); });
     fakeServiceManager->clear();
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 8555fd6..1a56ab7 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -147,6 +147,7 @@
     MOCK_METHOD(bool, getValidateSkipped, (HalDisplayId), (const, override));
     MOCK_METHOD(const aidl::android::hardware::graphics::composer3::OverlayProperties&,
                 getOverlaySupport, (), (const, override));
+    MOCK_METHOD(status_t, setRefreshRateChangedCallbackDebugEnabled, (PhysicalDisplayId, bool));
 };
 
 } // namespace mock
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index 9ad2edb..aa83883 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -1312,6 +1312,18 @@
                                  false);
 }
 
+TEST_F(OutputLayerWriteStateToHWCTest, setCompositionTypeRefreshRateIndicator) {
+    mLayerFEState.compositionType = Composition::REFRESH_RATE_INDICATOR;
+
+    expectGeometryCommonCalls();
+    expectPerFrameCommonCalls();
+    expectSetHdrMetadataAndBufferCalls();
+    expectSetCompositionTypeCall(Composition::REFRESH_RATE_INDICATOR);
+
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+}
+
 /*
  * OutputLayer::uncacheBuffers
  */
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index a89b23c..01db0cd 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -167,6 +167,7 @@
             .receivesInput = receivesInput(),
             .isSecure = isSecure(),
             .isPrimary = isPrimary(),
+            .isVirtual = isVirtual(),
             .rotationFlags = ui::Transform::toRotationFlags(mOrientation),
             .transformHint = getTransformHint()};
 }
@@ -420,8 +421,8 @@
                            capabilities.getDesiredMinLuminance());
 }
 
-void DisplayDevice::enableRefreshRateOverlay(bool enable, bool showSpinner, bool showRenderRate,
-                                             bool showInMiddle) {
+void DisplayDevice::enableRefreshRateOverlay(bool enable, bool setByHwc, bool showSpinner,
+                                             bool showRenderRate, bool showInMiddle) {
     if (!enable) {
         mRefreshRateOverlay.reset();
         return;
@@ -440,11 +441,22 @@
         features |= RefreshRateOverlay::Features::ShowInMiddle;
     }
 
+    if (setByHwc) {
+        features |= RefreshRateOverlay::Features::SetByHwc;
+    }
+
     const auto fpsRange = mRefreshRateSelector->getSupportedRefreshRateRange();
     mRefreshRateOverlay = std::make_unique<RefreshRateOverlay>(fpsRange, features);
     mRefreshRateOverlay->setLayerStack(getLayerStack());
     mRefreshRateOverlay->setViewport(getSize());
-    mRefreshRateOverlay->changeRefreshRate(getActiveMode().modePtr->getFps(), getActiveMode().fps);
+    updateRefreshRateOverlayRate(getActiveMode().modePtr->getFps(), getActiveMode().fps);
+}
+
+void DisplayDevice::updateRefreshRateOverlayRate(Fps displayFps, Fps renderFps, bool setByHwc) {
+    ATRACE_CALL();
+    if (mRefreshRateOverlay && (!mRefreshRateOverlay->isSetByHwc() || setByHwc)) {
+        mRefreshRateOverlay->changeRefreshRate(displayFps, renderFps);
+    }
 }
 
 bool DisplayDevice::onKernelTimerChanged(std::optional<DisplayModeId> desiredModeId,
@@ -453,7 +465,7 @@
         const auto newMode =
                 mRefreshRateSelector->onKernelTimerChanged(desiredModeId, timerExpired);
         if (newMode) {
-            mRefreshRateOverlay->changeRefreshRate(newMode->modePtr->getFps(), newMode->fps);
+            updateRefreshRateOverlayRate(newMode->modePtr->getFps(), newMode->fps);
             return true;
         }
     }
@@ -522,21 +534,21 @@
     mDesiredActiveModeChanged = false;
 }
 
-void DisplayDevice::adjustRefreshRate(Fps leaderDisplayRefreshRate) {
+void DisplayDevice::adjustRefreshRate(Fps pacesetterDisplayRefreshRate) {
     using fps_approx_ops::operator==;
     if (mRequestedRefreshRate == 0_Hz) {
         return;
     }
 
     using fps_approx_ops::operator>;
-    if (mRequestedRefreshRate > leaderDisplayRefreshRate) {
-        mAdjustedRefreshRate = leaderDisplayRefreshRate;
+    if (mRequestedRefreshRate > pacesetterDisplayRefreshRate) {
+        mAdjustedRefreshRate = pacesetterDisplayRefreshRate;
         return;
     }
 
     unsigned divisor = static_cast<unsigned>(
-            std::round(leaderDisplayRefreshRate.getValue() / mRequestedRefreshRate.getValue()));
-    mAdjustedRefreshRate = leaderDisplayRefreshRate / divisor;
+            std::round(pacesetterDisplayRefreshRate.getValue() / mRequestedRefreshRate.getValue()));
+    mAdjustedRefreshRate = pacesetterDisplayRefreshRate / divisor;
 }
 
 std::atomic<int32_t> DisplayDeviceState::sNextSequenceId(1);
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 710c582..51876e7 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -238,8 +238,9 @@
     }
 
     // Enables an overlay to be displayed with the current refresh rate
-    void enableRefreshRateOverlay(bool enable, bool showSpinner, bool showRenderRate,
+    void enableRefreshRateOverlay(bool enable, bool setByHwc, bool showSpinner, bool showRenderRate,
                                   bool showInMiddle) REQUIRES(kMainThreadContext);
+    void updateRefreshRateOverlayRate(Fps displayFps, Fps renderFps, bool setByHwc = false);
     bool isRefreshRateOverlayEnabled() const { return mRefreshRateOverlay != nullptr; }
     bool onKernelTimerChanged(std::optional<DisplayModeId>, bool timerExpired);
     void animateRefreshRateOverlay();
@@ -248,9 +249,9 @@
 
     Fps getAdjustedRefreshRate() const { return mAdjustedRefreshRate; }
 
-    // Round the requested refresh rate to match a divisor of the leader
+    // Round the requested refresh rate to match a divisor of the pacesetter
     // display's refresh rate. Only supported for virtual displays.
-    void adjustRefreshRate(Fps leaderDisplayRefreshRate);
+    void adjustRefreshRate(Fps pacesetterDisplayRefreshRate);
 
     // release HWC resources (if any) for removable displays
     void disconnect();
@@ -292,7 +293,7 @@
     // for virtual displays to match this requested refresh rate.
     const Fps mRequestedRefreshRate;
 
-    // Adjusted refresh rate, rounded to match a divisor of the leader
+    // Adjusted refresh rate, rounded to match a divisor of the pacesetter
     // display's refresh rate. Only supported for virtual displays.
     Fps mAdjustedRefreshRate = 0_Hz;
 
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 4194a7e..bd2680f 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -1428,6 +1428,19 @@
     return Error::NONE;
 }
 
+Error AidlComposer::setRefreshRateChangedCallbackDebugEnabled(Display displayId, bool enabled) {
+    const auto status =
+            mAidlComposerClient->setRefreshRateChangedCallbackDebugEnabled(translate<int64_t>(
+                                                                                   displayId),
+                                                                           enabled);
+    if (!status.isOk()) {
+        ALOGE("setRefreshRateChangedCallbackDebugEnabled failed %s",
+              status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    return Error::NONE;
+}
+
 Error AidlComposer::getClientTargetProperty(
         Display display, ClientTargetPropertyWithBrightness* outClientTargetProperty) {
     Error error = Error::NONE;
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
index d163ff2..8313c09 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -239,6 +239,7 @@
     void onHotplugDisconnect(Display) override;
     Error getHdrConversionCapabilities(std::vector<HdrConversionCapability>*) override;
     Error setHdrConversionStrategy(HdrConversionStrategy, Hdr*) override;
+    Error setRefreshRateChangedCallbackDebugEnabled(Display, bool) override;
 
 private:
     // Many public functions above simply write a command into the command
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index 9b9b7fd..c65c572 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -294,6 +294,7 @@
             std::vector<::aidl::android::hardware::graphics::common::HdrConversionCapability>*) = 0;
     virtual Error setHdrConversionStrategy(
             ::aidl::android::hardware::graphics::common::HdrConversionStrategy, Hdr*) = 0;
+    virtual Error setRefreshRateChangedCallbackDebugEnabled(Display, bool) = 0;
 };
 
 } // namespace Hwc2
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 470bf76..28148ac 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -808,6 +808,21 @@
     return NO_ERROR;
 }
 
+status_t HWComposer::setRefreshRateChangedCallbackDebugEnabled(PhysicalDisplayId displayId,
+                                                               bool enabled) {
+    RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
+    const auto error =
+            mComposer->setRefreshRateChangedCallbackDebugEnabled(mDisplayData[displayId]
+                                                                         .hwcDisplay->getId(),
+                                                                 enabled);
+    if (error != hal::Error::NONE) {
+        ALOGE("Error in setting refresh refresh rate change callback debug enabled %s",
+              to_string(error).c_str());
+        return INVALID_OPERATION;
+    }
+    return NO_ERROR;
+}
+
 status_t HWComposer::getDisplayDecorationSupport(
         PhysicalDisplayId displayId,
         std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 95568eb..7a3f41c 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -297,6 +297,7 @@
     virtual status_t setHdrConversionStrategy(
             aidl::android::hardware::graphics::common::HdrConversionStrategy,
             aidl::android::hardware::graphics::common::Hdr*) = 0;
+    virtual status_t setRefreshRateChangedCallbackDebugEnabled(PhysicalDisplayId, bool enabled) = 0;
 };
 
 static inline bool operator==(const android::HWComposer::DeviceRequestedChanges& lhs,
@@ -453,6 +454,7 @@
     status_t setHdrConversionStrategy(
             aidl::android::hardware::graphics::common::HdrConversionStrategy,
             aidl::android::hardware::graphics::common::Hdr*) override;
+    status_t setRefreshRateChangedCallbackDebugEnabled(PhysicalDisplayId, bool enabled) override;
 
     // for debugging ----------------------------------------------------------
     void dump(std::string& out) const override;
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index 9bc62b6..23de4fa 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -1358,6 +1358,10 @@
     return Error::UNSUPPORTED;
 }
 
+Error HidlComposer::setRefreshRateChangedCallbackDebugEnabled(Display, bool) {
+    return Error::UNSUPPORTED;
+}
+
 Error HidlComposer::getClientTargetProperty(
         Display display, ClientTargetPropertyWithBrightness* outClientTargetProperty) {
     IComposerClient::ClientTargetProperty property;
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
index 2bab1fe..d04652b 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -348,6 +348,7 @@
             override;
     Error setHdrConversionStrategy(aidl::android::hardware::graphics::common::HdrConversionStrategy,
                                    Hdr*) override;
+    Error setRefreshRateChangedCallbackDebugEnabled(Display, bool) override;
 
 private:
     class CommandWriter : public CommandWriterBase {
diff --git a/services/surfaceflinger/FrontEnd/DisplayInfo.h b/services/surfaceflinger/FrontEnd/DisplayInfo.h
index 6b9d7a2..76b36fe 100644
--- a/services/surfaceflinger/FrontEnd/DisplayInfo.h
+++ b/services/surfaceflinger/FrontEnd/DisplayInfo.h
@@ -30,6 +30,7 @@
     bool isSecure;
     // TODO(b/238781169) can eliminate once sPrimaryDisplayRotationFlags is removed.
     bool isPrimary;
+    bool isVirtual;
     ui::Transform::RotationFlags rotationFlags;
     ui::Transform::RotationFlags transformHint;
     std::string getDebugString() const {
diff --git a/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp b/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp
index 6659825..ce21233 100644
--- a/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp
@@ -65,7 +65,13 @@
     }
 }
 
-LayerCreationArgs::LayerCreationArgs(const LayerCreationArgs& args)
-      : LayerCreationArgs(args.flinger, args.client, args.name, args.flags, args.metadata) {}
+LayerCreationArgs::LayerCreationArgs(std::optional<uint32_t> id, bool internalLayer)
+      : LayerCreationArgs(nullptr, nullptr, /*name=*/"", /*flags=*/0, /*metadata=*/{}, id,
+                          internalLayer) {}
+
+LayerCreationArgs LayerCreationArgs::fromOtherArgs(const LayerCreationArgs& other) {
+    // returns a new instance of LayerCreationArgs with a unique id.
+    return LayerCreationArgs(other.flinger, other.client, other.name, other.flags, other.metadata);
+}
 
 } // namespace android::surfaceflinger
diff --git a/services/surfaceflinger/FrontEnd/LayerCreationArgs.h b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h
index 2cd6b55..011250c 100644
--- a/services/surfaceflinger/FrontEnd/LayerCreationArgs.h
+++ b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h
@@ -37,11 +37,13 @@
 struct LayerCreationArgs {
     static std::atomic<uint32_t> sSequence;
     static uint32_t getInternalLayerId(uint32_t id);
+    static LayerCreationArgs fromOtherArgs(const LayerCreationArgs& other);
 
     LayerCreationArgs(android::SurfaceFlinger*, sp<android::Client>, std::string name,
                       uint32_t flags, gui::LayerMetadata, std::optional<uint32_t> id = std::nullopt,
                       bool internalLayer = false);
-    LayerCreationArgs(const LayerCreationArgs&);
+    LayerCreationArgs(std::optional<uint32_t> id, bool internalLayer = false);
+    LayerCreationArgs() = default; // for tracing
 
     android::SurfaceFlinger* flinger;
     sp<android::Client> client;
@@ -56,6 +58,8 @@
     wp<IBinder> parentHandle = nullptr;
     wp<IBinder> mirrorLayerHandle = nullptr;
     ui::LayerStack layerStackToMirror = ui::INVALID_LAYER_STACK;
+    uint32_t parentId = UNASSIGNED_LAYER_ID;
+    uint32_t layerIdToMirror = UNASSIGNED_LAYER_ID;
 };
 
 } // namespace android::surfaceflinger
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
index 360a0a9..3706225 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
@@ -20,8 +20,7 @@
 #define LOG_TAG "LayerLifecycleManager"
 
 #include "LayerLifecycleManager.h"
-#include "Layer.h" // temporarily needed for LayerHandle
-#include "LayerHandle.h"
+#include "Client.h" // temporarily needed for LayerCreationArgs
 #include "LayerLog.h"
 #include "SwapErase.h"
 
@@ -72,12 +71,14 @@
     }
 }
 
-void LayerLifecycleManager::onHandlesDestroyed(const std::vector<uint32_t>& destroyedHandles) {
+void LayerLifecycleManager::onHandlesDestroyed(const std::vector<uint32_t>& destroyedHandles,
+                                               bool ignoreUnknownHandles) {
     std::vector<uint32_t> layersToBeDestroyed;
     for (const auto& layerId : destroyedHandles) {
         auto it = mIdToLayer.find(layerId);
         if (it == mIdToLayer.end()) {
-            LOG_ALWAYS_FATAL("%s Layerid not found %d", __func__, layerId);
+            LOG_ALWAYS_FATAL_IF(!ignoreUnknownHandles, "%s Layerid not found %d", __func__,
+                                layerId);
             continue;
         }
         RequestedLayerState& layer = it->second.owner;
@@ -167,7 +168,7 @@
     for (const auto& transaction : transactions) {
         for (const auto& resolvedComposerState : transaction.states) {
             const auto& clientState = resolvedComposerState.state;
-            uint32_t layerId = LayerHandle::getLayerId(clientState.surface);
+            uint32_t layerId = resolvedComposerState.layerId;
             if (layerId == UNASSIGNED_LAYER_ID) {
                 ALOGW("%s Handle %p is not valid", __func__, clientState.surface.get());
                 continue;
@@ -175,18 +176,21 @@
 
             RequestedLayerState* layer = getLayerFromId(layerId);
             if (layer == nullptr) {
-                LOG_ALWAYS_FATAL("%s Layer with handle %p (layerid=%d) not found", __func__,
-                                 clientState.surface.get(), layerId);
+                LOG_ALWAYS_FATAL("%s Layer with layerid=%d not found", __func__, layerId);
                 continue;
             }
 
             if (!layer->handleAlive) {
-                LOG_ALWAYS_FATAL("%s Layer's handle %p (layerid=%d) is not alive. Possible out of "
+                LOG_ALWAYS_FATAL("%s Layer's with layerid=%d) is not alive. Possible out of "
                                  "order LayerLifecycleManager updates",
-                                 __func__, clientState.surface.get(), layerId);
+                                 __func__, layerId);
                 continue;
             }
 
+            if (transaction.flags & ISurfaceComposer::eAnimation) {
+                layer->changes |= RequestedLayerState::Changes::Animation;
+            }
+
             uint32_t oldParentId = layer->parentId;
             uint32_t oldRelativeParentId = layer->relativeParentId;
             uint32_t oldTouchCropId = layer->touchCropId;
@@ -194,13 +198,11 @@
 
             if (layer->what & layer_state_t::eBackgroundColorChanged) {
                 if (layer->bgColorLayerId == UNASSIGNED_LAYER_ID && layer->bgColor.a != 0) {
-                    LayerCreationArgs backgroundLayerArgs{nullptr,
-                                                          nullptr,
-                                                          layer->name + "BackgroundColorLayer",
-                                                          ISurfaceComposerClient::eFXSurfaceEffect,
-                                                          {},
-                                                          layer->id,
-                                                          /*internalLayer=*/true};
+                    LayerCreationArgs backgroundLayerArgs(layer->id,
+                                                          /*internalLayer=*/true);
+                    backgroundLayerArgs.parentId = layer->id;
+                    backgroundLayerArgs.name = layer->name + "BackgroundColorLayer";
+                    backgroundLayerArgs.flags = ISurfaceComposerClient::eFXSurfaceEffect;
                     std::vector<std::unique_ptr<RequestedLayerState>> newLayers;
                     newLayers.emplace_back(
                             std::make_unique<RequestedLayerState>(backgroundLayerArgs));
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
index 25d27ee..3d9a74c 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
@@ -40,7 +40,10 @@
     // External state changes should be updated in the following order:
     void addLayers(std::vector<std::unique_ptr<RequestedLayerState>>);
     void applyTransactions(const std::vector<TransactionState>&);
-    void onHandlesDestroyed(const std::vector<uint32_t>&);
+    // Ignore unknown handles when iteroping with legacy front end. In the old world, we
+    // would create child layers which are not necessary with the new front end. This means
+    // we will get notified for handle changes that don't exist in the new front end.
+    void onHandlesDestroyed(const std::vector<uint32_t>&, bool ignoreUnknownHandles = false);
 
     // Detaches the layer from its relative parent to prevent a loop in the
     // layer hierarchy. This overrides the RequestedLayerState and leaves
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
index e069a63..6fb2f57 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -90,6 +90,7 @@
     scheduler::LayerInfo::FrameRate frameRate;
     ui::Transform::RotationFlags fixedTransformHint;
     bool handleSkipScreenshotFlag = false;
+    int32_t frameRateSelectionPriority;
     LayerHierarchy::TraversalPath mirrorRootPath;
     bool unreachable = true;
     ChildState childState;
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index 344dab4..a16de1b 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -274,7 +274,7 @@
     // InputDispatcher, and obviously if they aren't visible they can't occlude
     // anything.
     const bool visibleForInput =
-            (snapshot.inputInfo.token != nullptr) ? snapshot.canReceiveInput() : snapshot.isVisible;
+            snapshot.hasInputInfo() ? snapshot.canReceiveInput() : snapshot.isVisible;
     snapshot.inputInfo.setInputConfig(gui::WindowInfo::InputConfig::NOT_VISIBLE, !visibleForInput);
 }
 
@@ -773,6 +773,7 @@
         snapshot.layerOpaqueFlagSet =
                 (requested.flags & layer_state_t::eLayerOpaque) == layer_state_t::eLayerOpaque;
         snapshot.cachingHint = requested.cachingHint;
+        snapshot.frameRateSelectionPriority = requested.frameRateSelectionPriority;
     }
 
     if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::Content)) {
@@ -954,12 +955,13 @@
         snapshot.inputInfo = *requested.windowInfoHandle->getInfo();
     } else {
         snapshot.inputInfo = {};
+        // b/271132344 revisit this and see if we can always use the layers uid/pid
+        snapshot.inputInfo.name = requested.name;
+        snapshot.inputInfo.ownerUid = static_cast<int32_t>(requested.ownerUid);
+        snapshot.inputInfo.ownerPid = requested.ownerPid;
     }
 
-    snapshot.inputInfo.name = requested.name;
     snapshot.inputInfo.id = static_cast<int32_t>(snapshot.uniqueSequence);
-    snapshot.inputInfo.ownerUid = static_cast<int32_t>(requested.ownerUid);
-    snapshot.inputInfo.ownerPid = requested.ownerPid;
     snapshot.inputInfo.displayId = static_cast<int32_t>(snapshot.outputFilter.layerStack.id);
     if (!needsInputInfo(snapshot, requested)) {
         return;
@@ -983,7 +985,9 @@
     }
 
     snapshot.inputInfo.alpha = snapshot.color.a;
-    snapshot.inputInfo.touchOcclusionMode = parentSnapshot.inputInfo.touchOcclusionMode;
+    snapshot.inputInfo.touchOcclusionMode = requested.hasInputInfo()
+            ? requested.windowInfoHandle->getInfo()->touchOcclusionMode
+            : parentSnapshot.inputInfo.touchOcclusionMode;
     if (requested.dropInputMode == gui::DropInputMode::ALL ||
         parentSnapshot.dropInputMode == gui::DropInputMode::ALL) {
         snapshot.dropInputMode = gui::DropInputMode::ALL;
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index e2cbe28..a5fdaf4 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -24,7 +24,6 @@
 
 #include "Layer.h"
 #include "LayerCreationArgs.h"
-#include "LayerHandle.h"
 #include "LayerLog.h"
 #include "RequestedLayerState.h"
 
@@ -33,14 +32,6 @@
 using namespace ftl::flag_operators;
 
 namespace {
-uint32_t getLayerIdFromSurfaceControl(sp<SurfaceControl> surfaceControl) {
-    if (!surfaceControl) {
-        return UNASSIGNED_LAYER_ID;
-    }
-
-    return LayerHandle::getLayerId(surfaceControl->getHandle());
-}
-
 std::string layerIdToString(uint32_t layerId) {
     return layerId == UNASSIGNED_LAYER_ID ? "none" : std::to_string(layerId);
 }
@@ -64,17 +55,17 @@
         layerCreationFlags(args.flags),
         textureName(args.textureName),
         ownerUid(args.ownerUid),
-        ownerPid(args.ownerPid) {
+        ownerPid(args.ownerPid),
+        parentId(args.parentId),
+        layerIdToMirror(args.layerIdToMirror) {
     layerId = static_cast<int32_t>(args.sequence);
     changes |= RequestedLayerState::Changes::Created;
     metadata.merge(args.metadata);
     changes |= RequestedLayerState::Changes::Metadata;
     handleAlive = true;
-    parentId = LayerHandle::getLayerId(args.parentHandle.promote());
-    if (args.parentHandle != nullptr) {
+    if (parentId != UNASSIGNED_LAYER_ID) {
         canBeRoot = false;
     }
-    layerIdToMirror = LayerHandle::getLayerId(args.mirrorLayerHandle.promote());
     if (layerIdToMirror != UNASSIGNED_LAYER_ID) {
         changes |= RequestedLayerState::Changes::Mirror;
     } else if (args.layerStackToMirror != ui::INVALID_LAYER_STACK) {
@@ -209,7 +200,7 @@
     }
     if (clientState.what & layer_state_t::eReparent) {
         changes |= RequestedLayerState::Changes::Parent;
-        parentId = getLayerIdFromSurfaceControl(clientState.parentSurfaceControlForChild);
+        parentId = resolvedComposerState.parentId;
         parentSurfaceControlForChild = nullptr;
         // Once a layer has be reparented, it cannot be placed at the root. It sounds odd
         // but thats the existing logic and until we make this behavior more explicit, we need
@@ -218,7 +209,7 @@
     }
     if (clientState.what & layer_state_t::eRelativeLayerChanged) {
         changes |= RequestedLayerState::Changes::RelativeParent;
-        relativeParentId = getLayerIdFromSurfaceControl(clientState.relativeLayerSurfaceControl);
+        relativeParentId = resolvedComposerState.relativeParentId;
         isRelativeOf = true;
         relativeLayerSurfaceControl = nullptr;
     }
@@ -235,10 +226,8 @@
         changes |= RequestedLayerState::Changes::RelativeParent;
     }
     if (clientState.what & layer_state_t::eInputInfoChanged) {
-        wp<IBinder>& touchableRegionCropHandle =
-                windowInfoHandle->editInfo()->touchableRegionCropHandle;
-        touchCropId = LayerHandle::getLayerId(touchableRegionCropHandle.promote());
-        touchableRegionCropHandle.clear();
+        touchCropId = resolvedComposerState.touchCropId;
+        windowInfoHandle->editInfo()->touchableRegionCropHandle.clear();
     }
     if (clientState.what & layer_state_t::eStretchChanged) {
         stretchEffect.sanitize();
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
index 8b475a3..216e95f 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
@@ -53,6 +53,7 @@
         VisibleRegion = 1u << 14,
         Buffer = 1u << 15,
         SidebandStream = 1u << 16,
+        Animation = 1u << 17,
     };
     static Rect reduce(const Rect& win, const Region& exclude);
     RequestedLayerState(const LayerCreationArgs&);
@@ -104,17 +105,17 @@
     std::shared_ptr<renderengine::ExternalTexture> externalTexture;
     gui::GameMode gameMode;
     scheduler::LayerInfo::FrameRate requestedFrameRate;
-    ui::LayerStack layerStackToMirror = ui::INVALID_LAYER_STACK;
+    uint32_t parentId = UNASSIGNED_LAYER_ID;
+    uint32_t relativeParentId = UNASSIGNED_LAYER_ID;
     uint32_t layerIdToMirror = UNASSIGNED_LAYER_ID;
+    ui::LayerStack layerStackToMirror = ui::INVALID_LAYER_STACK;
+    uint32_t touchCropId = UNASSIGNED_LAYER_ID;
+    uint32_t bgColorLayerId = UNASSIGNED_LAYER_ID;
 
     // book keeping states
     bool handleAlive = true;
     bool isRelativeOf = false;
-    uint32_t parentId = UNASSIGNED_LAYER_ID;
-    uint32_t relativeParentId = UNASSIGNED_LAYER_ID;
     std::vector<uint32_t> mirrorIds{};
-    uint32_t touchCropId = UNASSIGNED_LAYER_ID;
-    uint32_t bgColorLayerId = UNASSIGNED_LAYER_ID;
     ftl::Flags<RequestedLayerState::Changes> changes;
     bool bgColorLayer = false;
 };
diff --git a/services/surfaceflinger/FrontEnd/Update.h b/services/surfaceflinger/FrontEnd/Update.h
new file mode 100644
index 0000000..e1449b6
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/Update.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <gui/DisplayInfo.h>
+
+#include "FrontEnd/LayerCreationArgs.h"
+#include "RequestedLayerState.h"
+#include "TransactionState.h"
+
+namespace android {
+struct LayerCreatedState {
+    LayerCreatedState(const wp<Layer>& layer, const wp<Layer>& parent, bool addToRoot)
+          : layer(layer), initialParent(parent), addToRoot(addToRoot) {}
+    wp<Layer> layer;
+    // Indicates the initial parent of the created layer, only used for creating layer in
+    // SurfaceFlinger. If nullptr, it may add the created layer into the current root layers.
+    wp<Layer> initialParent;
+    // Indicates whether the layer getting created should be added at root if there's no parent
+    // and has permission ACCESS_SURFACE_FLINGER. If set to false and no parent, the layer will
+    // be added offscreen.
+    bool addToRoot;
+};
+} // namespace android
+
+namespace android::surfaceflinger::frontend {
+
+// Atomic set of changes affecting layer state. These changes are queued in binder threads and
+// applied every vsync.
+struct Update {
+    std::vector<TransactionState> transactions;
+    std::vector<LayerCreatedState> layerCreatedStates;
+    std::vector<std::unique_ptr<frontend::RequestedLayerState>> newLayers;
+    std::vector<LayerCreationArgs> layerCreationArgs;
+    std::vector<uint32_t> destroyedHandles;
+};
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/HdrLayerInfoReporter.cpp b/services/surfaceflinger/HdrLayerInfoReporter.cpp
index c88554e..9eefbe4 100644
--- a/services/surfaceflinger/HdrLayerInfoReporter.cpp
+++ b/services/surfaceflinger/HdrLayerInfoReporter.cpp
@@ -40,7 +40,8 @@
 
     for (const auto& listener : toInvoke) {
         ATRACE_NAME("invoking onHdrLayerInfoChanged");
-        listener->onHdrLayerInfoChanged(info.numberOfHdrLayers, info.maxW, info.maxH, info.flags);
+        listener->onHdrLayerInfoChanged(info.numberOfHdrLayers, info.maxW, info.maxH, info.flags,
+                                        info.maxDesiredHdrSdrRatio);
     }
 }
 
diff --git a/services/surfaceflinger/HdrLayerInfoReporter.h b/services/surfaceflinger/HdrLayerInfoReporter.h
index 9b70c16..bf7c775 100644
--- a/services/surfaceflinger/HdrLayerInfoReporter.h
+++ b/services/surfaceflinger/HdrLayerInfoReporter.h
@@ -43,27 +43,18 @@
         // With peak display brightnesses exceeding 1,000 nits currently, HLG's request could
         // actually be satisfied in some ambient conditions such that limiting that max for that
         // content in theory makes sense
-        float maxDesiredSdrHdrRatio = 0.f;
+        float maxDesiredHdrSdrRatio = 0.f;
 
         bool operator==(const HdrLayerInfo& other) const {
             return numberOfHdrLayers == other.numberOfHdrLayers && maxW == other.maxW &&
-                    maxH == other.maxH && flags == other.flags;
+                    maxH == other.maxH && flags == other.flags &&
+                    maxDesiredHdrSdrRatio == other.maxDesiredHdrSdrRatio;
         }
 
         bool operator!=(const HdrLayerInfo& other) const { return !(*this == other); }
 
         void mergeDesiredRatio(float update) {
-            if (maxDesiredSdrHdrRatio == 0.f) {
-                // If nothing is set, take the incoming value
-                maxDesiredSdrHdrRatio = update;
-            } else if (update == 1.f) {
-                // If the request is to "go to max", then take it regardless
-                maxDesiredSdrHdrRatio = 1.f;
-            } else if (maxDesiredSdrHdrRatio != 1.f) {
-                // If we're not currently asked to "go to max", then take the max
-                // of the incoming requests
-                maxDesiredSdrHdrRatio = std::max(maxDesiredSdrHdrRatio, update);
-            }
+            maxDesiredHdrSdrRatio = std::max(maxDesiredHdrSdrRatio, update);
         }
     };
 
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 433606a..9521371 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -196,7 +196,8 @@
         mDrawingState.color.b = -1.0_hf;
     }
 
-    mFrameTracker.setDisplayRefreshPeriod(args.flinger->mScheduler->getLeaderVsyncPeriod().ns());
+    mFrameTracker.setDisplayRefreshPeriod(
+            args.flinger->mScheduler->getPacesetterVsyncPeriod().ns());
 
     mOwnerUid = args.ownerUid;
     mOwnerPid = args.ownerPid;
@@ -680,6 +681,9 @@
     } else if ((mDrawingState.flags & layer_state_t::eLayerIsDisplayDecoration) != 0) {
         snapshot->compositionType =
                 aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION;
+    } else if ((mDrawingState.flags & layer_state_t::eLayerIsRefreshRateIndicator) != 0) {
+        snapshot->compositionType =
+                aidl::android::hardware::graphics::composer3::Composition::REFRESH_RATE_INDICATOR;
     } else {
         // Normal buffer layers
         snapshot->hdrMetadata = mBufferInfo.mHdrMetadata;
@@ -1253,7 +1257,7 @@
         return parentFrameRate;
     }();
 
-    *transactionNeeded |= setFrameRateForLayerTree(frameRate);
+    *transactionNeeded |= setFrameRateForLayerTreeLegacy(frameRate);
 
     // The frame rate is propagated to the children
     bool childrenHaveFrameRate = false;
@@ -1267,7 +1271,7 @@
     if (!frameRate.rate.isValid() && frameRate.type != FrameRateCompatibility::NoVote &&
         childrenHaveFrameRate) {
         *transactionNeeded |=
-                setFrameRateForLayerTree(FrameRate(Fps(), FrameRateCompatibility::NoVote));
+                setFrameRateForLayerTreeLegacy(FrameRate(Fps(), FrameRateCompatibility::NoVote));
     }
 
     // We return whether this layer ot its children has a vote. We ignore ExactOrMultiple votes for
@@ -1419,7 +1423,7 @@
     return surfaceFrame;
 }
 
-bool Layer::setFrameRateForLayerTree(FrameRate frameRate) {
+bool Layer::setFrameRateForLayerTreeLegacy(FrameRate frameRate) {
     if (mDrawingState.frameRateForLayerTree == frameRate) {
         return false;
     }
@@ -1432,9 +1436,21 @@
     mDrawingState.modified = true;
     setTransactionFlags(eTransactionNeeded);
 
-    using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType;
-    mFlinger->mScheduler->recordLayerHistory(this, systemTime(), LayerUpdateType::SetFrameRate);
+    mFlinger->mScheduler
+            ->recordLayerHistory(sequence, getLayerProps(), systemTime(),
+                                 scheduler::LayerHistory::LayerUpdateType::SetFrameRate);
+    return true;
+}
 
+bool Layer::setFrameRateForLayerTree(FrameRate frameRate, const scheduler::LayerProps& layerProps) {
+    if (mDrawingState.frameRateForLayerTree == frameRate) {
+        return false;
+    }
+
+    mDrawingState.frameRateForLayerTree = frameRate;
+    mFlinger->mScheduler
+            ->recordLayerHistory(sequence, layerProps, systemTime(),
+                                 scheduler::LayerHistory::LayerUpdateType::SetFrameRate);
     return true;
 }
 
@@ -3037,6 +3053,10 @@
                                       mLastClientCompositionFence);
             mLastClientCompositionFence = nullptr;
         }
+    } else {
+        // if we are latching a buffer for the first time then clear the mLastLatchTime since
+        // we don't want to incorrectly classify a frame if we miss the desired present time.
+        updateLastLatchTime(0);
     }
 
     mDrawingState.producerId = bufferData.producerId;
@@ -3056,7 +3076,7 @@
     } else {
         mCallbackHandleAcquireTimeOrFence = mDrawingState.acquireFenceTime->getSignalTime();
     }
-
+    mDrawingState.latchedVsyncId = info.vsyncId;
     mDrawingState.modified = true;
     setTransactionFlags(eTransactionNeeded);
 
@@ -3066,18 +3086,9 @@
     mDrawingState.desiredPresentTime = desiredPresentTime;
     mDrawingState.isAutoTimestamp = isAutoTimestamp;
 
-    const nsecs_t presentTime = [&] {
-        if (!isAutoTimestamp) return desiredPresentTime;
-
-        const auto prediction =
-                mFlinger->mFrameTimeline->getTokenManager()->getPredictionsForToken(info.vsyncId);
-        if (prediction.has_value()) return prediction->presentTime;
-
-        return static_cast<nsecs_t>(0);
-    }();
-
-    using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType;
-    mFlinger->mScheduler->recordLayerHistory(this, presentTime, LayerUpdateType::Buffer);
+    if (mFlinger->mLegacyFrontEndEnabled) {
+        recordLayerHistoryBufferUpdate(getLayerProps());
+    }
 
     setFrameTimelineVsyncForBufferTransaction(info, postTime);
 
@@ -3094,6 +3105,32 @@
     return true;
 }
 
+void Layer::setDesiredPresentTime(nsecs_t desiredPresentTime, bool isAutoTimestamp) {
+    mDrawingState.desiredPresentTime = desiredPresentTime;
+    mDrawingState.isAutoTimestamp = isAutoTimestamp;
+}
+
+void Layer::recordLayerHistoryBufferUpdate(const scheduler::LayerProps& layerProps) {
+    const nsecs_t presentTime = [&] {
+        if (!mDrawingState.isAutoTimestamp) return mDrawingState.desiredPresentTime;
+
+        const auto prediction = mFlinger->mFrameTimeline->getTokenManager()->getPredictionsForToken(
+                mDrawingState.latchedVsyncId);
+        if (prediction.has_value()) return prediction->presentTime;
+
+        return static_cast<nsecs_t>(0);
+    }();
+    mFlinger->mScheduler->recordLayerHistory(sequence, layerProps, presentTime,
+                                             scheduler::LayerHistory::LayerUpdateType::Buffer);
+}
+
+void Layer::recordLayerHistoryAnimationTx(const scheduler::LayerProps& layerProps) {
+    const nsecs_t presentTime =
+            mDrawingState.isAutoTimestamp ? 0 : mDrawingState.desiredPresentTime;
+    mFlinger->mScheduler->recordLayerHistory(sequence, layerProps, presentTime,
+                                             scheduler::LayerHistory::LayerUpdateType::AnimationTX);
+}
+
 bool Layer::setDataspace(ui::Dataspace dataspace) {
     mDrawingState.dataspaceRequested = true;
     if (mDrawingState.dataspace == dataspace) return false;
@@ -3131,6 +3168,7 @@
 }
 
 bool Layer::setSurfaceDamageRegion(const Region& surfaceDamage) {
+    if (mDrawingState.surfaceDamageRegion.hasSameRects(surfaceDamage)) return false;
     mDrawingState.surfaceDamageRegion = surfaceDamage;
     mDrawingState.modified = true;
     setTransactionFlags(eTransactionNeeded);
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 4309aca..f7e1969 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -228,6 +228,7 @@
         float currentSdrHdrRatio = 1.f;
         float desiredSdrHdrRatio = 1.f;
         gui::CachingHint cachingHint = gui::CachingHint::Enabled;
+        int64_t latchedVsyncId = 0;
     };
 
     explicit Layer(const LayerCreationArgs& args);
@@ -305,6 +306,7 @@
                    const BufferData& /* bufferData */, nsecs_t /* postTime */,
                    nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
                    std::optional<nsecs_t> /* dequeueTime */, const FrameTimelineInfo& /*info*/);
+    void setDesiredPresentTime(nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/);
     bool setDataspace(ui::Dataspace /*dataspace*/);
     bool setExtendedRangeBrightness(float currentBufferRatio, float desiredRatio);
     bool setCachingHint(gui::CachingHint cachingHint);
@@ -846,12 +848,30 @@
     void updateMetadataSnapshot(const LayerMetadata& parentMetadata);
     void updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMetadata,
                                         std::unordered_set<Layer*>& visited);
+    sp<Layer> getClonedFrom() const {
+        return mClonedFrom != nullptr ? mClonedFrom.promote() : nullptr;
+    }
+    bool isClone() { return mClonedFrom != nullptr; }
+
     bool willPresentCurrentTransaction() const;
 
     void callReleaseBufferCallback(const sp<ITransactionCompletedListener>& listener,
                                    const sp<GraphicBuffer>& buffer, uint64_t framenumber,
                                    const sp<Fence>& releaseFence);
-    bool setFrameRateForLayerTree(FrameRate);
+    bool setFrameRateForLayerTreeLegacy(FrameRate);
+    bool setFrameRateForLayerTree(FrameRate, const scheduler::LayerProps&);
+    void recordLayerHistoryBufferUpdate(const scheduler::LayerProps&);
+    void recordLayerHistoryAnimationTx(const scheduler::LayerProps&);
+    auto getLayerProps() const {
+        return scheduler::LayerProps{
+                .visible = isVisible(),
+                .bounds = getBounds(),
+                .transform = getTransform(),
+                .setFrameRateVote = getFrameRateForLayerTree(),
+                .frameRateSelectionPriority = getFrameRateSelectionPriority(),
+        };
+    };
+    bool hasBuffer() const { return mBufferInfo.mBuffer != nullptr; }
 
 protected:
     // For unit tests
@@ -870,10 +890,6 @@
     void gatherBufferInfo();
     void onSurfaceFrameCreated(const std::shared_ptr<frametimeline::SurfaceFrame>&);
 
-    sp<Layer> getClonedFrom() const {
-        return mClonedFrom != nullptr ? mClonedFrom.promote() : nullptr;
-    }
-    bool isClone() { return mClonedFrom != nullptr; }
     bool isClonedFromAlive() { return getClonedFrom() != nullptr; }
 
     void cloneDrawingState(const Layer* from);
diff --git a/services/surfaceflinger/LayerProtoHelper.cpp b/services/surfaceflinger/LayerProtoHelper.cpp
index 55281fa..5c91b91 100644
--- a/services/surfaceflinger/LayerProtoHelper.cpp
+++ b/services/surfaceflinger/LayerProtoHelper.cpp
@@ -394,6 +394,22 @@
                                    [&]() { return layerInfo->mutable_destination_frame(); });
 }
 
+google::protobuf::RepeatedPtrField<DisplayProto> LayerProtoHelper::writeDisplayInfoToProto(
+        const display::DisplayMap<ui::LayerStack, frontend::DisplayInfo>& displayInfos) {
+    google::protobuf::RepeatedPtrField<DisplayProto> displays;
+    displays.Reserve(displayInfos.size());
+    for (const auto& [layerStack, displayInfo] : displayInfos) {
+        auto displayProto = displays.Add();
+        displayProto->set_id(displayInfo.info.displayId);
+        displayProto->set_layer_stack(layerStack.id);
+        displayProto->mutable_size()->set_w(displayInfo.info.logicalWidth);
+        displayProto->mutable_size()->set_h(displayInfo.info.logicalHeight);
+        writeTransformToProto(displayInfo.transform, displayProto->mutable_transform());
+        displayProto->set_is_virtual(displayInfo.isVirtual);
+    }
+    return displays;
+}
+
 } // namespace surfaceflinger
 } // namespace android
 
diff --git a/services/surfaceflinger/LayerProtoHelper.h b/services/surfaceflinger/LayerProtoHelper.h
index de4bd01..38d73f6 100644
--- a/services/surfaceflinger/LayerProtoHelper.h
+++ b/services/surfaceflinger/LayerProtoHelper.h
@@ -63,10 +63,11 @@
                                       const frontend::LayerSnapshotBuilder& snapshotBuilder,
                                       const std::unordered_map<uint32_t, sp<Layer>>& mLegacyLayers,
                                       uint32_t traceFlags);
-
     static void writeSnapshotToProto(LayerProto* outProto,
                                      const frontend::RequestedLayerState& requestedState,
                                      const frontend::LayerSnapshot& snapshot, uint32_t traceFlags);
+    static google::protobuf::RepeatedPtrField<DisplayProto> writeDisplayInfoToProto(
+            const display::DisplayMap<ui::LayerStack, frontend::DisplayInfo>& displayInfos);
 };
 
 } // namespace surfaceflinger
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index 0ade467..9a4261d 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -29,7 +29,6 @@
 #include <SkBlendMode.h>
 #include <SkRect.h>
 #include <SkSurface.h>
-#include <gui/SurfaceComposerClient.h>
 #include <gui/SurfaceControl.h>
 
 #undef LOG_TAG
@@ -46,15 +45,6 @@
 constexpr int kBufferWidth = kMaxDigits * kDigitWidth + (kMaxDigits - 1) * 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
 
 SurfaceControlHolder::~SurfaceControlHolder() {
@@ -242,7 +232,7 @@
         return;
     }
 
-    createTransaction(mSurfaceControl->get())
+    createTransaction()
             .setLayer(mSurfaceControl->get(), INT32_MAX - 2)
             .setTrustedOverlay(mSurfaceControl->get(), true)
             .apply();
@@ -272,14 +262,14 @@
         }
     }();
 
-    createTransaction(mSurfaceControl->get())
-            .setTransform(mSurfaceControl->get(), transform)
-            .apply();
+    createTransaction().setTransform(mSurfaceControl->get(), transform).apply();
 
     BufferCache::const_iterator it =
             mBufferCache.find({displayFps.getIntValue(), renderFps.getIntValue(), transformHint});
     if (it == mBufferCache.end()) {
-        const int minFps = mFpsRange.min.getIntValue();
+        // HWC minFps is not known by the framework in order
+        // to consider lower rates we set minFps to 0.
+        const int minFps = isSetByHwc() ? 0 : mFpsRange.min.getIntValue();
         const int maxFps = mFpsRange.max.getIntValue();
 
         // Clamp to the range. The current displayFps may be outside of this range if the display
@@ -327,7 +317,7 @@
         frame.offsetBy(width >> 1, height >> 4);
     }
 
-    createTransaction(mSurfaceControl->get())
+    createTransaction()
             .setMatrix(mSurfaceControl->get(), frame.getWidth() / static_cast<float>(kBufferWidth),
                        0, 0, frame.getHeight() / static_cast<float>(kBufferHeight))
             .setPosition(mSurfaceControl->get(), frame.left, frame.top)
@@ -335,14 +325,14 @@
 }
 
 void RefreshRateOverlay::setLayerStack(ui::LayerStack stack) {
-    createTransaction(mSurfaceControl->get()).setLayerStack(mSurfaceControl->get(), stack).apply();
+    createTransaction().setLayerStack(mSurfaceControl->get(), stack).apply();
 }
 
 void RefreshRateOverlay::changeRefreshRate(Fps displayFps, Fps renderFps) {
     mDisplayFps = displayFps;
     mRenderFps = renderFps;
     const auto buffer = getOrCreateBuffers(displayFps, renderFps)[mFrame];
-    createTransaction(mSurfaceControl->get()).setBuffer(mSurfaceControl->get(), buffer).apply();
+    createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply();
 }
 
 void RefreshRateOverlay::animate() {
@@ -351,7 +341,23 @@
     const auto& buffers = getOrCreateBuffers(*mDisplayFps, *mRenderFps);
     mFrame = (mFrame + 1) % buffers.size();
     const auto buffer = buffers[mFrame];
-    createTransaction(mSurfaceControl->get()).setBuffer(mSurfaceControl->get(), buffer).apply();
+    createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply();
+}
+
+SurfaceComposerClient::Transaction RefreshRateOverlay::createTransaction() const {
+    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;
+
+    const sp<SurfaceControl>& surface = mSurfaceControl->get();
+
+    SurfaceComposerClient::Transaction transaction;
+    if (isSetByHwc()) {
+        transaction.setFlags(surface, layer_state_t::eLayerIsRefreshRateIndicator,
+                             layer_state_t::eLayerIsRefreshRateIndicator);
+    }
+    transaction.setFrameRate(surface, kFrameRate, kCompatibility, kSeamlessness);
+    return transaction;
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h
index b68a88c..0b89b8e 100644
--- a/services/surfaceflinger/RefreshRateOverlay.h
+++ b/services/surfaceflinger/RefreshRateOverlay.h
@@ -21,6 +21,7 @@
 
 #include <ftl/flags.h>
 #include <ftl/small_map.h>
+#include <gui/SurfaceComposerClient.h>
 #include <ui/LayerStack.h>
 #include <ui/Size.h>
 #include <ui/Transform.h>
@@ -55,6 +56,7 @@
         Spinner = 1 << 0,
         RenderRate = 1 << 1,
         ShowInMiddle = 1 << 2,
+        SetByHwc = 1 << 3,
     };
 
     RefreshRateOverlay(FpsRange, ftl::Flags<Features>);
@@ -63,6 +65,7 @@
     void setViewport(ui::Size);
     void changeRefreshRate(Fps, Fps);
     void animate();
+    bool isSetByHwc() const { return mFeatures.test(RefreshRateOverlay::Features::SetByHwc); }
 
 private:
     using Buffers = std::vector<sp<GraphicBuffer>>;
@@ -82,6 +85,8 @@
 
     const Buffers& getOrCreateBuffers(Fps, Fps);
 
+    SurfaceComposerClient::Transaction createTransaction() const;
+
     struct Key {
         int displayFps;
         int renderFps;
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index e853833..beaf972 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -118,27 +118,17 @@
     }
 }
 
-void LayerHistory::record(Layer* layer, nsecs_t presentTime, nsecs_t now,
-                          LayerUpdateType updateType) {
+void LayerHistory::record(int32_t id, const LayerProps& layerProps, nsecs_t presentTime,
+                          nsecs_t now, LayerUpdateType updateType) {
     std::lock_guard lock(mLock);
-    auto id = layer->getSequence();
-
     auto [found, layerPair] = findLayer(id);
     if (found == LayerStatus::NotFound) {
         // Offscreen layer
-        ALOGV("%s: %s not registered", __func__, layer->getName().c_str());
+        ALOGV("%s: %d not registered", __func__, id);
         return;
     }
 
     const auto& info = layerPair->second;
-    const auto layerProps = LayerInfo::LayerProps{
-            .visible = layer->isVisible(),
-            .bounds = layer->getBounds(),
-            .transform = layer->getTransform(),
-            .setFrameRateVote = layer->getFrameRateForLayerTree(),
-            .frameRateSelectionPriority = layer->getFrameRateSelectionPriority(),
-    };
-
     info->setLastPresentTime(presentTime, now, updateType, mModeChangePending, layerProps);
 
     // Set frame rate to attached choreographer.
@@ -149,7 +139,7 @@
         while (it != range.second) {
             sp<EventThreadConnection> choreographerConnection = it->second.promote();
             if (choreographerConnection) {
-                choreographerConnection->frameRate = layer->getFrameRateForLayerTree().rate;
+                choreographerConnection->frameRate = layerProps.setFrameRateVote.rate;
                 it++;
             } else {
                 it = mAttachedChoreographers.erase(it);
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index 68e7030..69caf9f 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -38,6 +38,7 @@
 namespace scheduler {
 
 class LayerInfo;
+struct LayerProps;
 
 class LayerHistory {
 public:
@@ -63,7 +64,8 @@
     };
 
     // Marks the layer as active, and records the given state to its history.
-    void record(Layer*, nsecs_t presentTime, nsecs_t now, LayerUpdateType updateType);
+    void record(int32_t id, const LayerProps& props, nsecs_t presentTime, nsecs_t now,
+                LayerUpdateType updateType);
 
     // Updates the default frame rate compatibility which takes effect when the app
     // does not set a preference for refresh rate.
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 0142ccd..5a90d58 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -44,14 +44,17 @@
         mOwnerUid(ownerUid),
         mDefaultVote(defaultVote),
         mLayerVote({defaultVote, Fps()}),
-        mRefreshRateHistory(name) {}
+        mLayerProps(std::make_unique<LayerProps>()),
+        mRefreshRateHistory(name) {
+    ;
+}
 
 void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType,
-                                   bool pendingModeChange, LayerProps props) {
+                                   bool pendingModeChange, const LayerProps& props) {
     lastPresentTime = std::max(lastPresentTime, static_cast<nsecs_t>(0));
 
     mLastUpdatedTime = std::max(lastPresentTime, now);
-    mLayerProps = props;
+    *mLayerProps = props;
     switch (updateType) {
         case LayerUpdateType::AnimationTX:
             mLastAnimationTime = std::max(lastPresentTime, now);
@@ -305,6 +308,26 @@
     return mTraceTags.at(type).c_str();
 }
 
+LayerInfo::FrameRate LayerInfo::getSetFrameRateVote() const {
+    return mLayerProps->setFrameRateVote;
+}
+
+bool LayerInfo::isVisible() const {
+    return mLayerProps->visible;
+}
+
+int32_t LayerInfo::getFrameRateSelectionPriority() const {
+    return mLayerProps->frameRateSelectionPriority;
+}
+
+FloatRect LayerInfo::getBounds() const {
+    return mLayerProps->bounds;
+}
+
+ui::Transform LayerInfo::getTransform() const {
+    return mLayerProps->transform;
+}
+
 LayerInfo::RefreshRateHistory::HeuristicTraceTagData
 LayerInfo::RefreshRateHistory::makeHeuristicTraceTagData() const {
     const std::string prefix = "LFPS ";
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index 93485be..a3523ac 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -37,7 +37,7 @@
 namespace scheduler {
 
 using namespace std::chrono_literals;
-
+struct LayerProps;
 // Maximum period between presents for a layer to be considered active.
 constexpr std::chrono::nanoseconds MAX_ACTIVE_LAYER_PERIOD_NS = 1200ms;
 
@@ -132,19 +132,11 @@
     LayerInfo(const LayerInfo&) = delete;
     LayerInfo& operator=(const LayerInfo&) = delete;
 
-    struct LayerProps {
-        bool visible = false;
-        FloatRect bounds;
-        ui::Transform transform;
-        FrameRate setFrameRateVote;
-        int32_t frameRateSelectionPriority = -1;
-    };
-
     // Records the last requested present time. It also stores information about when
     // the layer was last updated. If the present time is farther in the future than the
     // updated time, the updated time is the present time.
     void setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType,
-                            bool pendingModeChange, LayerProps props);
+                            bool pendingModeChange, const LayerProps& props);
 
     // Sets an explicit layer vote. This usually comes directly from the application via
     // ANativeWindow_setFrameRate API
@@ -168,13 +160,11 @@
     // updated time, the updated time is the present time.
     nsecs_t getLastUpdatedTime() const { return mLastUpdatedTime; }
 
-    FrameRate getSetFrameRateVote() const { return mLayerProps.setFrameRateVote; }
-    bool isVisible() const { return mLayerProps.visible; }
-    int32_t getFrameRateSelectionPriority() const { return mLayerProps.frameRateSelectionPriority; }
-
-    FloatRect getBounds() const { return mLayerProps.bounds; }
-
-    ui::Transform getTransform() const { return mLayerProps.transform; }
+    FrameRate getSetFrameRateVote() const;
+    bool isVisible() const;
+    int32_t getFrameRateSelectionPriority() const;
+    FloatRect getBounds() const;
+    ui::Transform getTransform() const;
 
     // Returns a C string for tracing a vote
     const char* getTraceTag(LayerHistory::LayerVoteType type) const;
@@ -294,7 +284,7 @@
     static constexpr size_t HISTORY_SIZE = RefreshRateHistory::HISTORY_SIZE;
     static constexpr std::chrono::nanoseconds HISTORY_DURATION = 1s;
 
-    LayerProps mLayerProps;
+    std::unique_ptr<LayerProps> mLayerProps;
 
     RefreshRateHistory mRefreshRateHistory;
 
@@ -304,5 +294,13 @@
     static bool sTraceEnabled;
 };
 
+struct LayerProps {
+    bool visible = false;
+    FloatRect bounds;
+    ui::Transform transform;
+    LayerInfo::FrameRate setFrameRateVote;
+    int32_t frameRateSelectionPriority = -1;
+};
+
 } // namespace scheduler
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index f6fe468..eec7c08 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -308,7 +308,7 @@
         // significantly faster than the display rate, at it would cause a significant frame drop.
         // It is more appropriate to choose a higher display rate even if
         // a pull-down will be required.
-        constexpr float kMinMultiplier = 0.25f;
+        constexpr float kMinMultiplier = 0.75f;
         if (multiplier >= kMinMultiplier &&
             isFractionalPairOrMultiple(refreshRate, layer.desiredRefreshRate)) {
             return kScoreForFractionalPairs;
@@ -958,7 +958,7 @@
         }
 
         const bool ascending = (refreshRateOrder == RefreshRateOrder::Ascending);
-        const auto id = frameRateMode.modePtr->getId();
+        const auto id = modePtr->getId();
         if (ascending && frameRateMode.fps < *maxRenderRateForMode.get(id)) {
             // TODO(b/266481656): Once this bug is fixed, we can remove this workaround and actually
             //  use a lower frame rate when we want Ascending frame rates.
@@ -970,14 +970,20 @@
         if (ascending) {
             score = 1.0f / score;
         }
+
+        constexpr float kScore = std::numeric_limits<float>::max();
         if (preferredDisplayModeOpt) {
             if (*preferredDisplayModeOpt == modePtr->getId()) {
-                constexpr float kScore = std::numeric_limits<float>::max();
                 ranking.emplace_front(ScoredFrameRate{frameRateMode, kScore});
                 return;
             }
             constexpr float kNonPreferredModePenalty = 0.95f;
             score *= kNonPreferredModePenalty;
+        } else if (ascending && id == getMinRefreshRateByPolicyLocked()->getId()) {
+            // TODO(b/266481656): Once this bug is fixed, we can remove this workaround
+            //  and actually use a lower frame rate when we want Ascending frame rates.
+            ranking.emplace_front(ScoredFrameRate{frameRateMode, kScore});
+            return;
         }
 
         ALOGV("%s(%s) %s (%s) scored %.2f", whence, ftl::enum_string(refreshRateOrder).c_str(),
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 064f853..f18dfdc 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -81,7 +81,7 @@
     mTouchTimer.reset();
 
     // Stop idle timer and clear callbacks, as the RefreshRateSelector may outlive the Scheduler.
-    demoteLeaderDisplay();
+    demotePacesetterDisplay();
 }
 
 void Scheduler::startTimers() {
@@ -106,11 +106,11 @@
     }
 }
 
-void Scheduler::setLeaderDisplay(std::optional<PhysicalDisplayId> leaderIdOpt) {
-    demoteLeaderDisplay();
+void Scheduler::setPacesetterDisplay(std::optional<PhysicalDisplayId> pacesetterIdOpt) {
+    demotePacesetterDisplay();
 
     std::scoped_lock lock(mDisplayLock);
-    promoteLeaderDisplay(leaderIdOpt);
+    promotePacesetterDisplay(pacesetterIdOpt);
 }
 
 void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
@@ -121,17 +121,17 @@
 void Scheduler::registerDisplayInternal(PhysicalDisplayId displayId,
                                         RefreshRateSelectorPtr selectorPtr,
                                         std::shared_ptr<VsyncSchedule> vsyncSchedule) {
-    demoteLeaderDisplay();
+    demotePacesetterDisplay();
 
     std::scoped_lock lock(mDisplayLock);
     mRefreshRateSelectors.emplace_or_replace(displayId, std::move(selectorPtr));
     mVsyncSchedules.emplace_or_replace(displayId, std::move(vsyncSchedule));
 
-    promoteLeaderDisplay();
+    promotePacesetterDisplay();
 }
 
 void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) {
-    demoteLeaderDisplay();
+    demotePacesetterDisplay();
 
     std::scoped_lock lock(mDisplayLock);
     mRefreshRateSelectors.erase(displayId);
@@ -142,7 +142,7 @@
     // headless virtual display.)
     LOG_ALWAYS_FATAL_IF(mRefreshRateSelectors.empty(), "Cannot unregister all displays!");
 
-    promoteLeaderDisplay();
+    promotePacesetterDisplay();
 }
 
 void Scheduler::run() {
@@ -165,7 +165,7 @@
 
 std::optional<Fps> Scheduler::getFrameRateOverride(uid_t uid) const {
     const bool supportsFrameRateOverrideByContent =
-            leaderSelectorPtr()->supportsAppFrameRateOverrideByContent();
+            pacesetterSelectorPtr()->supportsAppFrameRateOverrideByContent();
     return mFrameRateOverrideMappings
             .getFrameRateOverrideForUid(uid, supportsFrameRateOverrideByContent);
 }
@@ -192,7 +192,7 @@
 
 impl::EventThread::GetVsyncPeriodFunction Scheduler::makeGetVsyncPeriodFunction() const {
     return [this](uid_t uid) {
-        const Fps refreshRate = leaderSelectorPtr()->getActiveMode().fps;
+        const Fps refreshRate = pacesetterSelectorPtr()->getActiveMode().fps;
         const nsecs_t currentPeriod =
                 getVsyncSchedule()->period().ns() ?: refreshRate.getPeriodNsecs();
 
@@ -285,7 +285,7 @@
 
 void Scheduler::onFrameRateOverridesChanged(ConnectionHandle handle, PhysicalDisplayId displayId) {
     const bool supportsFrameRateOverrideByContent =
-            leaderSelectorPtr()->supportsAppFrameRateOverrideByContent();
+            pacesetterSelectorPtr()->supportsAppFrameRateOverrideByContent();
 
     std::vector<FrameRateOverride> overrides =
             mFrameRateOverrideMappings.getAllFrameRateOverrides(supportsFrameRateOverrideByContent);
@@ -326,7 +326,7 @@
     // If the mode is not the current mode, this means that a
     // mode change is in progress. In that case we shouldn't dispatch an event
     // as it will be dispatched when the current mode changes.
-    if (leaderSelectorPtr()->getActiveMode() != mPolicy.modeOpt) {
+    if (pacesetterSelectorPtr()->getActiveMode() != mPolicy.modeOpt) {
         return;
     }
 
@@ -487,10 +487,10 @@
     mLayerHistory.deregisterLayer(layer);
 }
 
-void Scheduler::recordLayerHistory(Layer* layer, nsecs_t presentTime,
+void Scheduler::recordLayerHistory(int32_t id, const LayerProps& layerProps, nsecs_t presentTime,
                                    LayerHistory::LayerUpdateType updateType) {
-    if (leaderSelectorPtr()->canSwitch()) {
-        mLayerHistory.record(layer, presentTime, systemTime(), updateType);
+    if (pacesetterSelectorPtr()->canSwitch()) {
+        mLayerHistory.record(id, layerProps, presentTime, systemTime(), updateType);
     }
 }
 
@@ -504,7 +504,7 @@
 }
 
 void Scheduler::chooseRefreshRateForContent() {
-    const auto selectorPtr = leaderSelectorPtr();
+    const auto selectorPtr = pacesetterSelectorPtr();
     if (!selectorPtr->canSwitch()) return;
 
     ATRACE_CALL();
@@ -514,22 +514,22 @@
 }
 
 void Scheduler::resetIdleTimer() {
-    leaderSelectorPtr()->resetIdleTimer();
+    pacesetterSelectorPtr()->resetIdleTimer();
 }
 
 void Scheduler::onTouchHint() {
     if (mTouchTimer) {
         mTouchTimer->reset();
-        leaderSelectorPtr()->resetKernelIdleTimer();
+        pacesetterSelectorPtr()->resetKernelIdleTimer();
     }
 }
 
 void Scheduler::setDisplayPowerMode(PhysicalDisplayId id, hal::PowerMode powerMode) {
-    const bool isLeader = [this, id]() REQUIRES(kMainThreadContext) {
+    const bool isPacesetter = [this, id]() REQUIRES(kMainThreadContext) {
         ftl::FakeGuard guard(mDisplayLock);
-        return id == mLeaderDisplayId;
+        return id == mPacesetterDisplayId;
     }();
-    if (isLeader) {
+    if (isPacesetter) {
         // TODO (b/255657128): This needs to be handled per display.
         std::lock_guard<std::mutex> lock(mPolicyLock);
         mPolicy.displayPowerMode = powerMode;
@@ -539,7 +539,7 @@
         auto vsyncSchedule = getVsyncScheduleLocked(id);
         vsyncSchedule->getController().setDisplayPowerMode(powerMode);
     }
-    if (!isLeader) return;
+    if (!isPacesetter) return;
 
     if (mDisplayPowerTimer) {
         mDisplayPowerTimer->reset();
@@ -560,8 +560,8 @@
         std::optional<PhysicalDisplayId> idOpt) const {
     ftl::FakeGuard guard(kMainThreadContext);
     if (!idOpt) {
-        LOG_ALWAYS_FATAL_IF(!mLeaderDisplayId, "Missing a leader!");
-        idOpt = mLeaderDisplayId;
+        LOG_ALWAYS_FATAL_IF(!mPacesetterDisplayId, "Missing a pacesetter!");
+        idOpt = mPacesetterDisplayId;
     }
     auto scheduleOpt = mVsyncSchedules.get(*idOpt);
     LOG_ALWAYS_FATAL_IF(!scheduleOpt);
@@ -573,7 +573,7 @@
 
     // TODO(145561154): cleanup the kernel idle timer implementation and the refresh rate
     // magic number
-    const Fps refreshRate = leaderSelectorPtr()->getActiveMode().modePtr->getFps();
+    const Fps refreshRate = pacesetterSelectorPtr()->getActiveMode().modePtr->getFps();
 
     constexpr Fps FPS_THRESHOLD_FOR_KERNEL_TIMER = 65_Hz;
     using namespace fps_approx_ops;
@@ -637,7 +637,7 @@
         {
             std::scoped_lock lock(mDisplayLock);
             ftl::FakeGuard guard(kMainThreadContext);
-            dumper.dump("leaderDisplayId"sv, mLeaderDisplayId);
+            dumper.dump("pacesetterDisplayId"sv, mPacesetterDisplayId);
         }
         dumper.dump("layerHistory"sv, mLayerHistory.dump());
         dumper.dump("touchTimer"sv, mTouchTimer.transform(&OneShotTimer::interval));
@@ -651,13 +651,13 @@
 void Scheduler::dumpVsync(std::string& out) const {
     std::scoped_lock lock(mDisplayLock);
     ftl::FakeGuard guard(kMainThreadContext);
-    if (mLeaderDisplayId) {
-        base::StringAppendF(&out, "VsyncSchedule for leader %s:\n",
-                            to_string(*mLeaderDisplayId).c_str());
+    if (mPacesetterDisplayId) {
+        base::StringAppendF(&out, "VsyncSchedule for pacesetter %s:\n",
+                            to_string(*mPacesetterDisplayId).c_str());
         getVsyncScheduleLocked()->dump(out);
     }
     for (auto& [id, vsyncSchedule] : mVsyncSchedules) {
-        if (id == mLeaderDisplayId) {
+        if (id == mPacesetterDisplayId) {
             continue;
         }
         base::StringAppendF(&out, "VsyncSchedule for follower %s:\n", to_string(id).c_str());
@@ -669,31 +669,31 @@
     if (consideredSignals.idle) return false;
 
     const auto frameRateOverrides =
-            leaderSelectorPtr()->getFrameRateOverrides(mPolicy.contentRequirements,
-                                                       displayRefreshRate, consideredSignals);
+            pacesetterSelectorPtr()->getFrameRateOverrides(mPolicy.contentRequirements,
+                                                           displayRefreshRate, consideredSignals);
 
     // Note that RefreshRateSelector::supportsFrameRateOverrideByContent is checked when querying
     // the FrameRateOverrideMappings rather than here.
     return mFrameRateOverrideMappings.updateFrameRateOverridesByContent(frameRateOverrides);
 }
 
-void Scheduler::promoteLeaderDisplay(std::optional<PhysicalDisplayId> leaderIdOpt) {
-    // TODO(b/241286431): Choose the leader display.
-    mLeaderDisplayId = leaderIdOpt.value_or(mRefreshRateSelectors.begin()->first);
-    ALOGI("Display %s is the leader", to_string(*mLeaderDisplayId).c_str());
+void Scheduler::promotePacesetterDisplay(std::optional<PhysicalDisplayId> pacesetterIdOpt) {
+    // TODO(b/241286431): Choose the pacesetter display.
+    mPacesetterDisplayId = pacesetterIdOpt.value_or(mRefreshRateSelectors.begin()->first);
+    ALOGI("Display %s is the pacesetter", to_string(*mPacesetterDisplayId).c_str());
 
-    auto vsyncSchedule = getVsyncScheduleLocked(*mLeaderDisplayId);
-    if (const auto leaderPtr = leaderSelectorPtrLocked()) {
-        leaderPtr->setIdleTimerCallbacks(
+    auto vsyncSchedule = getVsyncScheduleLocked(*mPacesetterDisplayId);
+    if (const auto pacesetterPtr = pacesetterSelectorPtrLocked()) {
+        pacesetterPtr->setIdleTimerCallbacks(
                 {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); },
                               .onExpired = [this] { idleTimerCallback(TimerState::Expired); }},
                  .kernel = {.onReset = [this] { kernelIdleTimerCallback(TimerState::Reset); },
                             .onExpired =
                                     [this] { kernelIdleTimerCallback(TimerState::Expired); }}});
 
-        leaderPtr->startIdleTimer();
+        pacesetterPtr->startIdleTimer();
 
-        const Fps refreshRate = leaderPtr->getActiveMode().modePtr->getFps();
+        const Fps refreshRate = pacesetterPtr->getActiveMode().modePtr->getFps();
         vsyncSchedule->startPeriodTransition(mSchedulerCallback, refreshRate.getPeriod(),
                                              true /* force */);
     }
@@ -707,14 +707,14 @@
     }
 }
 
-void Scheduler::demoteLeaderDisplay() {
+void Scheduler::demotePacesetterDisplay() {
     // No need to lock for reads on kMainThreadContext.
-    if (const auto leaderPtr = FTL_FAKE_GUARD(mDisplayLock, leaderSelectorPtrLocked())) {
-        leaderPtr->stopIdleTimer();
-        leaderPtr->clearIdleTimerCallbacks();
+    if (const auto pacesetterPtr = FTL_FAKE_GUARD(mDisplayLock, pacesetterSelectorPtrLocked())) {
+        pacesetterPtr->stopIdleTimer();
+        pacesetterPtr->clearIdleTimerCallbacks();
     }
 
-    // Clear state that depends on the leader's RefreshRateSelector.
+    // Clear state that depends on the pacesetter's RefreshRateSelector.
     std::scoped_lock lock(mPolicyLock);
     mPolicy = {};
 }
@@ -743,10 +743,11 @@
 
             modeChoices = chooseDisplayModes();
 
-            // TODO(b/240743786): The leader display's mode must change for any DisplayModeRequest
-            // to go through. Fix this by tracking per-display Scheduler::Policy and timers.
+            // TODO(b/240743786): The pacesetter display's mode must change for any
+            // DisplayModeRequest to go through. Fix this by tracking per-display Scheduler::Policy
+            // and timers.
             std::tie(modeOpt, consideredSignals) =
-                    modeChoices.get(*mLeaderDisplayId)
+                    modeChoices.get(*mPacesetterDisplayId)
                             .transform([](const DisplayModeChoice& choice) {
                                 return std::make_pair(choice.mode, choice.consideredSignals);
                             })
@@ -879,7 +880,7 @@
 FrameRateMode Scheduler::getPreferredDisplayMode() {
     std::lock_guard<std::mutex> lock(mPolicyLock);
     const auto frameRateMode =
-            leaderSelectorPtr()
+            pacesetterSelectorPtr()
                     ->getRankedFrameRates(mPolicy.contentRequirements, makeGlobalSignals())
                     .ranking.front()
                     .frameRateMode;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 7374054..74547d5 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -102,8 +102,8 @@
 
     void startTimers();
 
-    // TODO(b/241285191): Remove this API by promoting leader in onScreen{Acquired,Released}.
-    void setLeaderDisplay(std::optional<PhysicalDisplayId>) REQUIRES(kMainThreadContext)
+    // TODO(b/241285191): Remove this API by promoting pacesetter in onScreen{Acquired,Released}.
+    void setPacesetterDisplay(std::optional<PhysicalDisplayId>) REQUIRES(kMainThreadContext)
             EXCLUDES(mDisplayLock);
 
     using RefreshRateSelectorPtr = std::shared_ptr<RefreshRateSelector>;
@@ -165,11 +165,11 @@
     void setDuration(ConnectionHandle, std::chrono::nanoseconds workDuration,
                      std::chrono::nanoseconds readyDuration);
 
-    const VsyncModulator& vsyncModulator() const { return *mVsyncModulator; }
+    VsyncModulator& vsyncModulator() { return *mVsyncModulator; }
 
-    // In some cases, we should only modulate for the leader display. In those
+    // In some cases, we should only modulate for the pacesetter display. In those
     // cases, the caller should pass in the relevant display, and the method
-    // will no-op if it's not the leader. Other cases are not specific to a
+    // will no-op if it's not the pacesetter. Other cases are not specific to a
     // display.
     template <typename... Args,
               typename Handler = std::optional<VsyncConfig> (VsyncModulator::*)(Args...)>
@@ -177,13 +177,13 @@
         if (id) {
             std::scoped_lock lock(mDisplayLock);
             ftl::FakeGuard guard(kMainThreadContext);
-            if (id != mLeaderDisplayId) {
+            if (id != mPacesetterDisplayId) {
                 return;
             }
         }
 
         if (const auto config = (*mVsyncModulator.*handler)(args...)) {
-            setVsyncConfig(*config, getLeaderVsyncPeriod());
+            setVsyncConfig(*config, getPacesetterVsyncPeriod());
         }
     }
 
@@ -219,8 +219,8 @@
 
     // Layers are registered on creation, and unregistered when the weak reference expires.
     void registerLayer(Layer*);
-    void recordLayerHistory(Layer*, nsecs_t presentTime, LayerHistory::LayerUpdateType)
-            EXCLUDES(mDisplayLock);
+    void recordLayerHistory(int32_t id, const LayerProps& layerProps, nsecs_t presentTime,
+                            LayerHistory::LayerUpdateType) EXCLUDES(mDisplayLock);
     void setModeChangePending(bool pending);
     void setDefaultFrameRateCompatibility(Layer*);
     void deregisterLayer(Layer*);
@@ -254,7 +254,7 @@
     void dump(ConnectionHandle, std::string&) const;
     void dumpVsync(std::string&) const EXCLUDES(mDisplayLock);
 
-    // Returns the preferred refresh rate and frame rate for the leader display.
+    // Returns the preferred refresh rate and frame rate for the pacesetter display.
     FrameRateMode getPreferredDisplayMode();
 
     // Notifies the scheduler about a refresh rate timeline change.
@@ -277,12 +277,12 @@
     // Retrieves the overridden refresh rate for a given uid.
     std::optional<Fps> getFrameRateOverride(uid_t) const EXCLUDES(mDisplayLock);
 
-    Period getLeaderVsyncPeriod() const EXCLUDES(mDisplayLock) {
-        return leaderSelectorPtr()->getActiveMode().fps.getPeriod();
+    Period getPacesetterVsyncPeriod() const EXCLUDES(mDisplayLock) {
+        return pacesetterSelectorPtr()->getActiveMode().fps.getPeriod();
     }
 
-    Fps getLeaderRefreshRate() const EXCLUDES(mDisplayLock) {
-        return leaderSelectorPtr()->getActiveMode().fps;
+    Fps getPacesetterRefreshRate() const EXCLUDES(mDisplayLock) {
+        return pacesetterSelectorPtr()->getActiveMode().fps;
     }
 
     // Returns the framerate of the layer with the given sequence ID
@@ -318,14 +318,14 @@
     void resyncAllToHardwareVsync(bool allowToEnable) EXCLUDES(mDisplayLock);
     void setVsyncConfig(const VsyncConfig&, Period vsyncPeriod);
 
-    // Chooses a leader among the registered displays, unless `leaderIdOpt` is specified. The new
-    // `mLeaderDisplayId` is never `std::nullopt`.
-    void promoteLeaderDisplay(std::optional<PhysicalDisplayId> leaderIdOpt = std::nullopt)
+    // Chooses a pacesetter among the registered displays, unless `pacesetterIdOpt` is specified.
+    // The new `mPacesetterDisplayId` is never `std::nullopt`.
+    void promotePacesetterDisplay(std::optional<PhysicalDisplayId> pacesetterIdOpt = std::nullopt)
             REQUIRES(kMainThreadContext, mDisplayLock);
 
-    // Blocks until the leader's idle timer thread exits. `mDisplayLock` must not be locked by the
-    // caller on the main thread to avoid deadlock, since the timer thread locks it before exit.
-    void demoteLeaderDisplay() REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock, mPolicyLock);
+    // Blocks until the pacesetter's idle timer thread exits. `mDisplayLock` must not be locked by
+    // the caller on the main thread to avoid deadlock, since the timer thread locks it before exit.
+    void demotePacesetterDisplay() REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock, mPolicyLock);
 
     void registerDisplayInternal(PhysicalDisplayId, RefreshRateSelectorPtr,
                                  std::shared_ptr<VsyncSchedule>) REQUIRES(kMainThreadContext)
@@ -415,23 +415,23 @@
     display::PhysicalDisplayMap<PhysicalDisplayId, std::shared_ptr<VsyncSchedule>> mVsyncSchedules
             GUARDED_BY(mDisplayLock) GUARDED_BY(kMainThreadContext);
 
-    ftl::Optional<PhysicalDisplayId> mLeaderDisplayId GUARDED_BY(mDisplayLock)
+    ftl::Optional<PhysicalDisplayId> mPacesetterDisplayId GUARDED_BY(mDisplayLock)
             GUARDED_BY(kMainThreadContext);
 
-    RefreshRateSelectorPtr leaderSelectorPtr() const EXCLUDES(mDisplayLock) {
+    RefreshRateSelectorPtr pacesetterSelectorPtr() const EXCLUDES(mDisplayLock) {
         std::scoped_lock lock(mDisplayLock);
-        return leaderSelectorPtrLocked();
+        return pacesetterSelectorPtrLocked();
     }
 
-    RefreshRateSelectorPtr leaderSelectorPtrLocked() const REQUIRES(mDisplayLock) {
+    RefreshRateSelectorPtr pacesetterSelectorPtrLocked() const REQUIRES(mDisplayLock) {
         ftl::FakeGuard guard(kMainThreadContext);
-        const RefreshRateSelectorPtr noLeader;
-        return mLeaderDisplayId
-                .and_then([this](PhysicalDisplayId leaderId)
+        const RefreshRateSelectorPtr noPacesetter;
+        return mPacesetterDisplayId
+                .and_then([this](PhysicalDisplayId pacesetterId)
                                   REQUIRES(mDisplayLock, kMainThreadContext) {
-                                      return mRefreshRateSelectors.get(leaderId);
+                                      return mRefreshRateSelectors.get(pacesetterId);
                                   })
-                .value_or(std::cref(noLeader));
+                .value_or(std::cref(noPacesetter));
     }
 
     std::shared_ptr<const VsyncSchedule> getVsyncScheduleLocked(
diff --git a/services/surfaceflinger/Scheduler/VsyncModulator.cpp b/services/surfaceflinger/Scheduler/VsyncModulator.cpp
index c9af4c2..586357f 100644
--- a/services/surfaceflinger/Scheduler/VsyncModulator.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncModulator.cpp
@@ -187,9 +187,9 @@
     static_cast<void>(updateVsyncConfigLocked());
 }
 
-bool VsyncModulator::isVsyncConfigDefault() const {
+bool VsyncModulator::isVsyncConfigEarly() const {
     std::lock_guard<std::mutex> lock(mMutex);
-    return getNextVsyncConfigType() == VsyncConfigType::Late;
+    return getNextVsyncConfigType() != VsyncConfigType::Late;
 }
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VsyncModulator.h b/services/surfaceflinger/Scheduler/VsyncModulator.h
index dc4dafd..be0d334 100644
--- a/services/surfaceflinger/Scheduler/VsyncModulator.h
+++ b/services/surfaceflinger/Scheduler/VsyncModulator.h
@@ -53,8 +53,12 @@
 
     explicit VsyncModulator(const VsyncConfigSet&, Now = Clock::now);
 
+    bool isVsyncConfigEarly() const EXCLUDES(mMutex);
+
     VsyncConfig getVsyncConfig() const EXCLUDES(mMutex);
 
+    void cancelRefreshRateChange() { mRefreshRateChangePending = false; }
+
     [[nodiscard]] VsyncConfig setVsyncConfigSet(const VsyncConfigSet&) EXCLUDES(mMutex);
 
     // Changes offsets in response to transaction flags or commit.
@@ -72,8 +76,6 @@
 
     [[nodiscard]] VsyncConfigOpt onDisplayRefresh(bool usedGpuComposition);
 
-    [[nodiscard]] bool isVsyncConfigDefault() const;
-
 protected:
     // Called from unit tests as well
     void binderDied(const wp<IBinder>&) override EXCLUDES(mMutex);
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
index 62e37db..84671ae 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
@@ -176,10 +176,16 @@
 
 void VsyncSchedule::disableHardwareVsync(ISchedulerCallback& callback, bool disallow) {
     std::lock_guard<std::mutex> lock(mHwVsyncLock);
-    if (mHwVsyncState == HwVsyncState::Enabled) {
-        callback.setVsyncEnabled(mId, false);
+    switch (mHwVsyncState) {
+        case HwVsyncState::Enabled:
+            callback.setVsyncEnabled(mId, false);
+            [[fallthrough]];
+        case HwVsyncState::Disabled:
+            mHwVsyncState = disallow ? HwVsyncState::Disallowed : HwVsyncState::Disabled;
+            break;
+        case HwVsyncState::Disallowed:
+            break;
     }
-    mHwVsyncState = disallow ? HwVsyncState::Disallowed : HwVsyncState::Disabled;
 }
 
 bool VsyncSchedule::isHardwareVsyncAllowed(bool makeAllowed) {
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 5a6221a..639ea27 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -105,6 +105,7 @@
 #include <memory>
 #include <mutex>
 #include <optional>
+#include <string>
 #include <type_traits>
 #include <unordered_map>
 #include <vector>
@@ -130,6 +131,7 @@
 #include "FrameTracer/FrameTracer.h"
 #include "FrontEnd/LayerCreationArgs.h"
 #include "FrontEnd/LayerHandle.h"
+#include "FrontEnd/LayerLifecycleManager.h"
 #include "FrontEnd/LayerSnapshot.h"
 #include "HdrLayerInfoReporter.h"
 #include "Layer.h"
@@ -292,6 +294,13 @@
             displayHdrCapabilities.getDesiredMinLuminance()};
 }
 
+uint32_t getLayerIdFromSurfaceControl(sp<SurfaceControl> surfaceControl) {
+    if (!surfaceControl) {
+        return UNASSIGNED_LAYER_ID;
+    }
+    return LayerHandle::getLayerId(surfaceControl->getHandle());
+}
+
 }  // namespace anonymous
 
 // ---------------------------------------------------------------------------
@@ -456,6 +465,7 @@
         android::hardware::details::setTrebleTestingOverride(true);
     }
 
+    // TODO (b/270966065) Update the HWC based refresh rate overlay to support spinner
     mRefreshRateOverlaySpinner = property_get_bool("debug.sf.show_refresh_rate_overlay_spinner", 0);
     mRefreshRateOverlayRenderRate =
             property_get_bool("debug.sf.show_refresh_rate_overlay_render_rate", 0);
@@ -473,9 +483,9 @@
             {.late = base::GetBoolProperty("debug.sf.send_late_power_session_hint"s, true),
              .early = base::GetBoolProperty("debug.sf.send_early_power_session_hint"s, false)};
     mLayerLifecycleManagerEnabled =
-            base::GetBoolProperty("debug.sf.enable_layer_lifecycle_manager"s, false);
+            base::GetBoolProperty("persist.debug.sf.enable_layer_lifecycle_manager"s, false);
     mLegacyFrontEndEnabled = !mLayerLifecycleManagerEnabled ||
-            base::GetBoolProperty("debug.sf.enable_legacy_frontend"s, true);
+            base::GetBoolProperty("persist.debug.sf.enable_legacy_frontend"s, false);
 }
 
 LatchUnsignaledConfig SurfaceFlinger::getLatchUnsignaledConfig() {
@@ -496,14 +506,13 @@
     // the window manager died on us. prepare its eulogy.
     mBootFinished = false;
 
-    // Sever the link to inputflinger since it's gone as well.
-    static_cast<void>(mScheduler->schedule(
-            [this] { mInputFlinger.clear(); }));
+    static_cast<void>(mScheduler->schedule([this]() FTL_FAKE_GUARD(kMainThreadContext) {
+        // Sever the link to inputflinger since it's gone as well.
+        mInputFlinger.clear();
 
-    // restore initial conditions (default device unblank, etc)
-    initializeDisplays();
+        initializeDisplays();
+    }));
 
-    // restart the boot-animation
     startBootAnim();
 }
 
@@ -872,7 +881,9 @@
     mDrawingState = mCurrentState;
 
     onActiveDisplayChangedLocked(nullptr, *display);
-    initializeDisplays();
+
+    static_cast<void>(mScheduler->schedule(
+            [this]() FTL_FAKE_GUARD(kMainThreadContext) { initializeDisplays(); }));
 
     mPowerAdvisor->init();
 
@@ -1588,8 +1599,8 @@
     const auto aidlConversionCapability = getHwComposer().getHdrConversionCapabilities();
     for (auto capability : aidlConversionCapability) {
         gui::HdrConversionCapability tempCapability;
-        tempCapability.sourceType = static_cast<int>(capability.sourceType.hdr);
-        tempCapability.outputType = static_cast<int>(capability.outputType->hdr);
+        tempCapability.sourceType = static_cast<int>(capability.sourceType);
+        tempCapability.outputType = static_cast<int>(capability.outputType);
         tempCapability.addsLatency = capability.addsLatency;
         hdrConversionCapabilities->push_back(tempCapability);
     }
@@ -2098,8 +2109,24 @@
     mScheduler->forceNextResync();
 }
 
-void SurfaceFlinger::onRefreshRateChangedDebug(const RefreshRateChangedDebugData&) {
-    // TODO(b/202734676) update refresh rate value on the RefreshRateOverlay
+void SurfaceFlinger::onRefreshRateChangedDebug(const RefreshRateChangedDebugData& data) {
+    ATRACE_CALL();
+    if (const auto displayId = getHwComposer().toPhysicalDisplayId(data.display); displayId) {
+        const Fps fps = Fps::fromPeriodNsecs(data.vsyncPeriodNanos);
+        ATRACE_FORMAT("%s Fps %d", __func__, fps.getIntValue());
+        static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
+            {
+                {
+                    const auto display = getDisplayDeviceLocked(*displayId);
+                    FTL_FAKE_GUARD(kMainThreadContext,
+                                   display->updateRefreshRateOverlayRate(fps,
+                                                                         display->getActiveMode()
+                                                                                 .fps,
+                                                                         /* setByHwc */ true));
+                }
+            }
+        }));
+    }
 }
 
 void SurfaceFlinger::setVsyncEnabled(PhysicalDisplayId id, bool enabled) {
@@ -2158,7 +2185,7 @@
     }
 }
 
-bool SurfaceFlinger::updateLayerSnapshotsLegacy(VsyncId vsyncId, LifecycleUpdate& update,
+bool SurfaceFlinger::updateLayerSnapshotsLegacy(VsyncId vsyncId, frontend::Update& update,
                                                 bool transactionsFlushed,
                                                 bool& outTransactionsAreEmpty) {
     bool needsTraversal = false;
@@ -2178,7 +2205,39 @@
     return mustComposite;
 }
 
-bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, LifecycleUpdate& update,
+void SurfaceFlinger::updateLayerHistory(const frontend::LayerSnapshot& snapshot) {
+    using Changes = frontend::RequestedLayerState::Changes;
+    if (snapshot.path.isClone() ||
+        !snapshot.changes.any(Changes::FrameRate | Changes::Buffer | Changes::Animation)) {
+        return;
+    }
+
+    const auto layerProps = scheduler::LayerProps{
+            .visible = snapshot.isVisible,
+            .bounds = snapshot.geomLayerBounds,
+            .transform = snapshot.geomLayerTransform,
+            .setFrameRateVote = snapshot.frameRate,
+            .frameRateSelectionPriority = snapshot.frameRateSelectionPriority,
+    };
+
+    auto it = mLegacyLayers.find(snapshot.sequence);
+    LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s",
+                        snapshot.getDebugString().c_str());
+
+    if (snapshot.changes.test(Changes::Animation)) {
+        it->second->recordLayerHistoryAnimationTx(layerProps);
+    }
+
+    if (snapshot.changes.test(Changes::FrameRate)) {
+        it->second->setFrameRateForLayerTree(snapshot.frameRate, layerProps);
+    }
+
+    if (snapshot.changes.test(Changes::Buffer)) {
+        it->second->recordLayerHistoryBufferUpdate(layerProps);
+    }
+}
+
+bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, frontend::Update& update,
                                           bool transactionsFlushed, bool& outTransactionsAreEmpty) {
     using Changes = frontend::RequestedLayerState::Changes;
     ATRACE_NAME("updateLayerSnapshots");
@@ -2219,7 +2278,7 @@
     }
 
     if (mLayerLifecycleManager.getGlobalChanges().any(Changes::Geometry | Changes::Input |
-                                                      Changes::Hierarchy)) {
+                                                      Changes::Hierarchy | Changes::Visibility)) {
         mUpdateInputInfo = true;
     }
     if (mLayerLifecycleManager.getGlobalChanges().any(Changes::VisibleRegion | Changes::Hierarchy |
@@ -2257,6 +2316,7 @@
         }
 
         for (auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) {
+            updateLayerHistory(*snapshot);
             if (!snapshot->hasReadyFrame) continue;
             newDataLatched = true;
             if (!snapshot->isVisible) break;
@@ -2427,9 +2487,14 @@
                                     Fps::fromPeriodNsecs(vsyncPeriod.ns()));
 
         const bool flushTransactions = clearTransactionFlags(eTransactionFlushNeeded);
-        LifecycleUpdate updates;
+        frontend::Update updates;
         if (flushTransactions) {
             updates = flushLifecycleUpdates();
+            if (mTransactionTracing) {
+                mTransactionTracing->addCommittedTransactions(vsyncId.value, frameTime.ns(),
+                                                              updates, mFrontEndDisplayInfos,
+                                                              mFrontEndDisplayInfosChanged);
+            }
         }
         bool transactionsAreEmpty;
         if (mLegacyFrontEndEnabled) {
@@ -2471,7 +2536,7 @@
 
     if (mLayerTracingEnabled && !mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) {
         // This will block and tracing should only be enabled for debugging.
-        mLayerTracing.notify(mVisibleRegionsDirty, frameTime.ns(), vsyncId.value);
+        addToLayerTracing(mVisibleRegionsDirty, frameTime.ns(), vsyncId.value);
     }
     mLastCommittedVsyncId = vsyncId;
 
@@ -2577,7 +2642,9 @@
         CompositionResult compositionResult{layerFE->stealCompositionResult()};
         layer->onPreComposition(compositionResult.refreshStartTime);
         for (auto releaseFence : compositionResult.releaseFences) {
-            layer->onLayerDisplayed(releaseFence);
+            Layer* clonedFrom = layer->getClonedFrom().get();
+            auto owningLayer = clonedFrom ? clonedFrom : layer;
+            owningLayer->onLayerDisplayed(releaseFence);
         }
         if (compositionResult.lastClientCompositionFence) {
             layer->setWasClientComposed(compositionResult.lastClientCompositionFence);
@@ -2643,7 +2710,7 @@
     mLayersWithQueuedFrames.clear();
     if (mLayerTracingEnabled && mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) {
         // This will block and should only be used for debugging.
-        mLayerTracing.notify(mVisibleRegionsDirty, frameTime.ns(), vsyncId.value);
+        addToLayerTracing(mVisibleRegionsDirty, frameTime.ns(), vsyncId.value);
     }
 
     if (mVisibleRegionsDirty) mHdrLayerInfoChanged = true;
@@ -2833,7 +2900,12 @@
                         const auto* outputLayer =
                             compositionDisplay->getOutputLayerForLayer(layerFe);
                         if (outputLayer) {
-                            info.mergeDesiredRatio(snapshot.desiredSdrHdrRatio);
+                            // TODO(b/267350616): Rename SdrHdrRatio -> HdrSdrRatio
+                            // everywhere
+                            const float desiredHdrSdrRatio = snapshot.desiredSdrHdrRatio <= 1.f
+                                    ? std::numeric_limits<float>::infinity()
+                                    : snapshot.desiredSdrHdrRatio;
+                            info.mergeDesiredRatio(desiredHdrSdrRatio);
                             info.numberOfHdrLayers++;
                             const auto displayFrame = outputLayer->getState().displayFrame;
                             const int32_t area = displayFrame.width() * displayFrame.height();
@@ -3367,7 +3439,7 @@
     }
 
     if (display->isVirtual()) {
-        display->adjustRefreshRate(mScheduler->getLeaderRefreshRate());
+        display->adjustRefreshRate(mScheduler->getPacesetterRefreshRate());
     }
 
     mDisplays.try_emplace(displayToken, std::move(display));
@@ -3436,7 +3508,7 @@
 
             // TODO(b/175678251) Call a listener instead.
             if (currentState.physical->hwcDisplayId == getHwComposer().getPrimaryHwcDisplayId()) {
-                updateActiveDisplayVsyncLocked(*display);
+                resetPhaseConfiguration(display->getActiveMode().fps);
             }
         }
         return;
@@ -3470,9 +3542,11 @@
     }
 }
 
-void SurfaceFlinger::updateActiveDisplayVsyncLocked(const DisplayDevice& activeDisplay) {
+void SurfaceFlinger::resetPhaseConfiguration(Fps refreshRate) {
+    // Cancel the pending refresh rate change, if any, before updating the phase configuration.
+    mScheduler->vsyncModulator().cancelRefreshRateChange();
+
     mVsyncConfiguration->reset();
-    const Fps refreshRate = activeDisplay.getActiveMode().fps;
     updatePhaseConfiguration(refreshRate);
     mRefreshRateStats->setRefreshRate(refreshRate);
 }
@@ -3955,8 +4029,14 @@
             mLayersWithQueuedFrames.emplace(sp<Layer>::fromExisting(layer));
         } else {
             layer->useEmptyDamage();
-            // If the layer has frames we will update the latch time when latching the buffer.
-            layer->updateLastLatchTime(latchTime);
+            if (!layer->hasBuffer()) {
+                // The last latch time is used to classify a missed frame as buffer stuffing
+                // instead of a missed frame. This is used to identify scenarios where we
+                // could not latch a buffer or apply a transaction due to backpressure.
+                // We only update the latch time for buffer less layers here, the latch time
+                // is updated for buffer layers when the buffer is latched.
+                layer->updateLastLatchTime(latchTime);
+            }
         }
     });
     mForceTransactionDisplayChange = false;
@@ -4006,7 +4086,7 @@
     return !mLayersWithQueuedFrames.empty() && newDataLatched;
 }
 
-status_t SurfaceFlinger::addClientLayer(const LayerCreationArgs& args, const sp<IBinder>& handle,
+status_t SurfaceFlinger::addClientLayer(LayerCreationArgs& args, const sp<IBinder>& handle,
                                         const sp<Layer>& layer, const wp<Layer>& parent,
                                         uint32_t* outTransformHint) {
     if (mNumLayers >= MAX_LAYERS) {
@@ -4036,11 +4116,15 @@
     if (outTransformHint) {
         *outTransformHint = mActiveDisplayTransformHint;
     }
-
+    args.parentId = LayerHandle::getLayerId(args.parentHandle.promote());
+    args.layerIdToMirror = LayerHandle::getLayerId(args.mirrorLayerHandle.promote());
     {
         std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
         mCreatedLayers.emplace_back(layer, parent, args.addToRoot);
         mNewLayers.emplace_back(std::make_unique<frontend::RequestedLayerState>(args));
+        args.mirrorLayerHandle.clear();
+        args.parentHandle.clear();
+        mNewLayerArgs.emplace_back(std::move(args));
     }
 
     setTransactionFlags(eTransactionNeeded);
@@ -4107,7 +4191,12 @@
         const TransactionHandler::TransactionFlushState& flushState) {
     using TransactionReadiness = TransactionHandler::TransactionReadiness;
     auto ready = TransactionReadiness::Ready;
-    flushState.transaction->traverseStatesWithBuffersWhileTrue([&](const layer_state_t& s) -> bool {
+    flushState.transaction->traverseStatesWithBuffersWhileTrue([&](const layer_state_t& s,
+                                                                   const std::shared_ptr<
+                                                                           renderengine::
+                                                                                   ExternalTexture>&
+                                                                           externalTexture)
+                                                                       -> bool {
         sp<Layer> layer = LayerHandle::getLayer(s.surface);
         const auto& transaction = *flushState.transaction;
         // check for barrier frames
@@ -4117,7 +4206,8 @@
             // don't wait on the barrier since we know that's stale information.
             if (layer->getDrawingState().producerId > s.bufferData->producerId) {
                 layer->callReleaseBufferCallback(s.bufferData->releaseBufferListener,
-                                                 s.bufferData->buffer, s.bufferData->frameNumber,
+                                                 externalTexture->getBuffer(),
+                                                 s.bufferData->frameNumber,
                                                  s.bufferData->acquireFence);
                 // Delete the entire state at this point and not just release the buffer because
                 // everything associated with the Layer in this Transaction is now out of date.
@@ -4200,7 +4290,7 @@
 
 bool SurfaceFlinger::applyTransactions(std::vector<TransactionState>& transactions,
                                        VsyncId vsyncId) {
-    Mutex::Autolock _l(mStateLock);
+    Mutex::Autolock lock(mStateLock);
     return applyTransactionsLocked(transactions, vsyncId);
 }
 
@@ -4219,10 +4309,6 @@
                                       transaction.listenerCallbacks, transaction.originPid,
                                       transaction.originUid, transaction.id);
     }
-
-    if (mTransactionTracing) {
-        mTransactionTracing->addCommittedTransactions(transactions, vsyncId.value);
-    }
     return needsTraversal;
 }
 
@@ -4281,9 +4367,8 @@
         // We don't want to latch unsignaled if are in early / client composition
         // as it leads to jank due to RenderEngine waiting for unsignaled buffer
         // or window animations being slow.
-        const auto isDefaultVsyncConfig = mScheduler->vsyncModulator().isVsyncConfigDefault();
-        if (!isDefaultVsyncConfig) {
-            ALOGV("%s: false (LatchUnsignaledConfig::AutoSingleLayer; !isDefaultVsyncConfig)",
+        if (mScheduler->vsyncModulator().isVsyncConfigEarly()) {
+            ALOGV("%s: false (LatchUnsignaledConfig::AutoSingleLayer; isVsyncConfigEarly)",
                   __func__);
             return false;
         }
@@ -4362,6 +4447,21 @@
                                                      layerName.c_str(), transactionId);
             mBufferCountTracker.increment(resolvedState.state.surface->localBinder());
         }
+        resolvedState.layerId = LayerHandle::getLayerId(resolvedState.state.surface);
+        if (resolvedState.state.what & layer_state_t::eReparent) {
+            resolvedState.parentId =
+                    getLayerIdFromSurfaceControl(resolvedState.state.parentSurfaceControlForChild);
+        }
+        if (resolvedState.state.what & layer_state_t::eRelativeLayerChanged) {
+            resolvedState.relativeParentId =
+                    getLayerIdFromSurfaceControl(resolvedState.state.relativeLayerSurfaceControl);
+        }
+        if (resolvedState.state.what & layer_state_t::eInputInfoChanged) {
+            wp<IBinder>& touchableRegionCropHandle =
+                    resolvedState.state.windowInfoHandle->editInfo()->touchableRegionCropHandle;
+            resolvedState.touchCropId =
+                    LayerHandle::getLayerId(touchableRegionCropHandle.promote());
+        }
     }
 
     TransactionState state{frameTimelineInfo,
@@ -4436,10 +4536,14 @@
         }
         if ((flags & eAnimation) && resolvedState.state.surface) {
             if (const auto layer = LayerHandle::getLayer(resolvedState.state.surface)) {
-                using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType;
-                mScheduler->recordLayerHistory(layer.get(),
-                                               isAutoTimestamp ? 0 : desiredPresentTime,
-                                               LayerUpdateType::AnimationTX);
+                const auto layerProps = scheduler::LayerProps{
+                        .visible = layer->isVisible(),
+                        .bounds = layer->getBounds(),
+                        .transform = layer->getTransform(),
+                        .setFrameRateVote = layer->getFrameRateForLayerTree(),
+                        .frameRateSelectionPriority = layer->getFrameRateSelectionPriority(),
+                };
+                layer->recordLayerHistoryAnimationTx(layerProps);
             }
         }
     }
@@ -4482,7 +4586,7 @@
 
 bool SurfaceFlinger::applyAndCommitDisplayTransactionStates(
         std::vector<TransactionState>& transactions) {
-    Mutex::Autolock _l(mStateLock);
+    Mutex::Autolock lock(mStateLock);
     bool needsTraversal = false;
     uint32_t transactionFlags = 0;
     for (auto& transaction : transactions) {
@@ -4900,6 +5004,10 @@
         layer->setFrameTimelineVsyncForBufferlessTransaction(frameTimelineInfo, postTime);
     }
 
+    if ((what & layer_state_t::eBufferChanged) == 0) {
+        layer->setDesiredPresentTime(desiredPresentTime, isAutoTimestamp);
+    }
+
     if (what & layer_state_t::eTrustedPresentationInfoChanged) {
         if (layer->setTrustedPresentationInfo(s.trustedPresentationThresholds,
                                               s.trustedPresentationListener)) {
@@ -5008,6 +5116,10 @@
         layer->setFrameTimelineVsyncForBufferlessTransaction(frameTimelineInfo, postTime);
     }
 
+    if ((what & layer_state_t::eBufferChanged) == 0) {
+        layer->setDesiredPresentTime(desiredPresentTime, isAutoTimestamp);
+    }
+
     if (what & layer_state_t::eTrustedPresentationInfoChanged) {
         if (layer->setTrustedPresentationInfo(s.trustedPresentationThresholds,
                                               s.trustedPresentationListener)) {
@@ -5021,16 +5133,6 @@
     if (layer->setTransactionCompletedListeners(callbackHandles, willPresentCurrentTransaction))
         flags |= eTraversalNeeded;
 
-    for (auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) {
-        if (snapshot->path.isClone() ||
-            !snapshot->changes.test(frontend::RequestedLayerState::Changes::FrameRate))
-            continue;
-        auto it = mLegacyLayers.find(snapshot->sequence);
-        LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s",
-                            snapshot->getDebugString().c_str());
-        it->second->setFrameRateForLayerTree(snapshot->frameRate);
-    }
-
     return flags;
 }
 
@@ -5048,7 +5150,7 @@
 
     sp<Layer> mirrorLayer;
     sp<Layer> mirrorFrom;
-    LayerCreationArgs mirrorArgs(args);
+    LayerCreationArgs mirrorArgs = LayerCreationArgs::fromOtherArgs(args);
     {
         Mutex::Autolock _l(mStateLock);
         mirrorFrom = LayerHandle::getLayer(mirrorFromHandle);
@@ -5068,11 +5170,6 @@
 
     outResult.layerId = mirrorLayer->sequence;
     outResult.layerName = String16(mirrorLayer->getDebugName());
-    if (mTransactionTracing) {
-        mTransactionTracing->onMirrorLayerAdded(outResult.handle->localBinder(),
-                                                mirrorLayer->sequence, args.name,
-                                                mirrorFrom->sequence);
-    }
     return addClientLayer(mirrorArgs, outResult.handle, mirrorLayer /* layer */,
                           nullptr /* parent */, nullptr /* outTransformHint */);
 }
@@ -5099,7 +5196,7 @@
         }
 
         layerStack = display->getLayerStack();
-        LayerCreationArgs mirrorArgs(args);
+        LayerCreationArgs mirrorArgs = LayerCreationArgs::fromOtherArgs(args);
         mirrorArgs.flags |= ISurfaceComposerClient::eNoColorFill;
         mirrorArgs.addToRoot = true;
         mirrorArgs.layerStackToMirror = layerStack;
@@ -5114,11 +5211,6 @@
         return result;
     }
 
-    if (mTransactionTracing) {
-        mTransactionTracing->onLayerAdded(outResult.handle->localBinder(), outResult.layerId,
-                                          args.name, args.flags, -1 /* parentId */);
-    }
-
     if (mLegacyFrontEndEnabled) {
         std::scoped_lock<std::mutex> lock(mMirrorDisplayLock);
         mMirrorDisplays.emplace_back(layerStack, outResult.handle, args.client);
@@ -5166,12 +5258,6 @@
         args.addToRoot = false;
     }
 
-    const int parentId = parent ? parent->getSequence() : -1;
-    if (mTransactionTracing) {
-        mTransactionTracing->onLayerAdded(outResult.handle->localBinder(), layer->sequence,
-                                          args.name, args.flags, parentId);
-    }
-
     uint32_t outTransformHint;
     result = addClientLayer(args, outResult.handle, layer, parent, &outTransformHint);
     if (result != NO_ERROR) {
@@ -5215,15 +5301,12 @@
     markLayerPendingRemovalLocked(layer);
     mBufferCountTracker.remove(handle);
     layer.clear();
-    if (mTransactionTracing) {
-        mTransactionTracing->onHandleRemoved(handle);
-    }
 
     setTransactionFlags(eTransactionFlushNeeded);
 }
 
-void SurfaceFlinger::onInitializeDisplays() {
-    const auto display = getDefaultDisplayDeviceLocked();
+void SurfaceFlinger::initializeDisplays() {
+    const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked());
     if (!display) return;
 
     const sp<IBinder> token = display->getDisplayToken().promote();
@@ -5231,13 +5314,13 @@
 
     TransactionState state;
     state.inputWindowCommands = mInputWindowCommands;
-    nsecs_t now = systemTime();
+    const nsecs_t now = systemTime();
     state.desiredPresentTime = now;
     state.postTime = now;
     state.permissions = layer_state_t::ACCESS_SURFACE_FLINGER;
     state.originPid = mPid;
     state.originUid = static_cast<int>(getuid());
-    uint64_t transactionId = (((uint64_t)mPid) << 32) | mUniqueTransactionId++;
+    const uint64_t transactionId = (static_cast<uint64_t>(mPid) << 32) | mUniqueTransactionId++;
     state.id = transactionId;
 
     // reset screen orientation and use primary layer stack
@@ -5257,21 +5340,16 @@
     std::vector<TransactionState> transactions;
     transactions.emplace_back(state);
 
-    // It should be on the main thread, apply it directly.
     if (mLegacyFrontEndEnabled) {
-        applyTransactionsLocked(transactions, /*vsyncId=*/{0});
+        applyTransactions(transactions, VsyncId{0});
     } else {
         applyAndCommitDisplayTransactionStates(transactions);
     }
 
-    setPowerModeInternal(display, hal::PowerMode::ON);
-}
-
-void SurfaceFlinger::initializeDisplays() {
-    // Async since we may be called from the main thread.
-    static_cast<void>(mScheduler->schedule(
-            [this]() FTL_FAKE_GUARD(mStateLock)
-                    FTL_FAKE_GUARD(kMainThreadContext) { onInitializeDisplays(); }));
+    {
+        ftl::FakeGuard guard(mStateLock);
+        setPowerModeInternal(display, hal::PowerMode::ON);
+    }
 }
 
 void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal::PowerMode mode) {
@@ -5473,7 +5551,8 @@
             LayersTraceProto* layersTrace = traceFileProto.add_entry();
             LayersProto layersProto = dumpProtoFromMainThread();
             layersTrace->mutable_layers()->Swap(&layersProto);
-            dumpDisplayProto(*layersTrace);
+            auto displayProtos = dumpDisplayProto();
+            layersTrace->mutable_displays()->Swap(&displayProtos);
 
             if (asProto) {
                 result.append(traceFileProto.SerializeAsString());
@@ -5725,9 +5804,10 @@
     return layersProto;
 }
 
-void SurfaceFlinger::dumpDisplayProto(LayersTraceProto& layersTraceProto) const {
+google::protobuf::RepeatedPtrField<DisplayProto> SurfaceFlinger::dumpDisplayProto() const {
+    google::protobuf::RepeatedPtrField<DisplayProto> displays;
     for (const auto& [_, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
-        DisplayProto* displayProto = layersTraceProto.add_displays();
+        DisplayProto* displayProto = displays.Add();
         displayProto->set_id(display->getId().value);
         displayProto->set_name(display->getDisplayName());
         displayProto->set_layer_stack(display->getLayerStack().id);
@@ -5740,6 +5820,7 @@
                                                 displayProto->mutable_transform());
         displayProto->set_is_virtual(display->isVirtual());
     }
+    return displays;
 }
 
 void SurfaceFlinger::dumpHwc(std::string& result) const {
@@ -6270,9 +6351,10 @@
                         int64_t startingTime =
                                 (fixedStartingTime) ? fixedStartingTime : systemTime();
                         mScheduler
-                                ->schedule([&]() FTL_FAKE_GUARD(mStateLock) {
-                                    mLayerTracing.notify(true /* visibleRegionDirty */,
-                                                         startingTime, mLastCommittedVsyncId.value);
+                                ->schedule([&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(
+                                                   kMainThreadContext) {
+                                    addToLayerTracing(true /* visibleRegionDirty */, startingTime,
+                                                      mLastCommittedVsyncId.value);
                                 })
                                 .wait();
                     }
@@ -7116,6 +7198,8 @@
         captureResults.capturedHdrLayers |= isHdrLayer(*snapshot);
         layerFE->mSnapshot->geomLayerTransform =
                 renderArea->getTransform() * layerFE->mSnapshot->geomLayerTransform;
+        layerFE->mSnapshot->geomInverseLayerTransform =
+                layerFE->mSnapshot->geomLayerTransform.inverse();
     }
 
     // We allow the system server to take screenshots of secure layers for
@@ -7524,10 +7608,20 @@
 }
 
 void SurfaceFlinger::enableRefreshRateOverlay(bool enable) {
+    bool setByHwc = getHwComposer().hasCapability(Capability::REFRESH_RATE_CHANGED_CALLBACK_DEBUG);
     for (const auto& [id, display] : mPhysicalDisplays) {
         if (display.snapshot().connectionType() == ui::DisplayConnectionType::Internal) {
+            if (setByHwc) {
+                const auto status =
+                        getHwComposer().setRefreshRateChangedCallbackDebugEnabled(id, enable);
+                if (status != NO_ERROR) {
+                    ALOGE("Error updating the refresh rate changed callback debug enabled");
+                    return;
+                }
+            }
+
             if (const auto device = getDisplayDeviceLocked(id)) {
-                device->enableRefreshRateOverlay(enable, mRefreshRateOverlaySpinner,
+                device->enableRefreshRateOverlay(enable, setByHwc, mRefreshRateOverlaySpinner,
                                                  mRefreshRateOverlayRenderRate,
                                                  mRefreshRateOverlayShowInMiddle);
             }
@@ -7623,10 +7717,6 @@
     if (hintDisplay) {
         layer->updateTransformHint(hintDisplay->getTransformHint());
     }
-
-    if (mTransactionTracing) {
-        mTransactionTracing->onLayerAddedToDrawingState(layer->getSequence(), vsyncId.value);
-    }
 }
 
 void SurfaceFlinger::sample() {
@@ -7646,26 +7736,32 @@
                                                   const DisplayDevice& activeDisplay) {
     ATRACE_CALL();
 
+    // For the first display activated during boot, there is no need to force setDesiredActiveMode,
+    // because DM is about to send its policy via setDesiredDisplayModeSpecs.
+    bool forceApplyPolicy = false;
+
     if (inactiveDisplayPtr) {
         inactiveDisplayPtr->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(false);
+        forceApplyPolicy = true;
     }
 
     mActiveDisplayId = activeDisplay.getPhysicalId();
     activeDisplay.getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true);
 
-    updateActiveDisplayVsyncLocked(activeDisplay);
+    resetPhaseConfiguration(activeDisplay.getActiveMode().fps);
+
     mScheduler->setModeChangePending(false);
-    mScheduler->setLeaderDisplay(mActiveDisplayId);
+    mScheduler->setPacesetterDisplay(mActiveDisplayId);
 
     onActiveDisplaySizeChanged(activeDisplay);
     mActiveDisplayTransformHint = activeDisplay.getTransformHint();
 
-    // The policy of the new active/leader display may have changed while it was inactive. In that
-    // case, its preferred mode has not been propagated to HWC (via setDesiredActiveMode). In either
-    // case, the Scheduler's cachedModeChangedParams must be initialized to the newly active mode,
-    // and the kernel idle timer of the newly active display must be toggled.
-    constexpr bool kForce = true;
-    applyRefreshRateSelectorPolicy(mActiveDisplayId, activeDisplay.refreshRateSelector(), kForce);
+    // The policy of the new active/pacesetter display may have changed while it was inactive. In
+    // that case, its preferred mode has not been propagated to HWC (via setDesiredActiveMode). In
+    // either case, the Scheduler's cachedModeChangedParams must be initialized to the newly active
+    // mode, and the kernel idle timer of the newly active display must be toggled.
+    applyRefreshRateSelectorPolicy(mActiveDisplayId, activeDisplay.refreshRateSelector(),
+                                   forceApplyPolicy);
 }
 
 status_t SurfaceFlinger::addWindowInfosListener(
@@ -7761,10 +7857,6 @@
             sp<Layer> childMirror;
             createEffectLayer(mirrorArgs, &unused, &childMirror);
             childMirror->setClonedChild(layer->createClone());
-            if (mTransactionTracing) {
-                mTransactionTracing->onLayerAddedToDrawingState(childMirror->getSequence(),
-                                                                vsyncId.value);
-            }
             childMirror->reparent(mirrorDisplay.rootHandle);
         }
     }
@@ -7949,8 +8041,8 @@
     };
 }
 
-SurfaceFlinger::LifecycleUpdate SurfaceFlinger::flushLifecycleUpdates() {
-    LifecycleUpdate update;
+frontend::Update SurfaceFlinger::flushLifecycleUpdates() {
+    frontend::Update update;
     ATRACE_NAME("TransactionHandler:flushTransactions");
     // Locking:
     // 1. to prevent onHandleDestroyed from being called while the state lock is held,
@@ -7967,12 +8059,28 @@
         mCreatedLayers.clear();
         update.newLayers = std::move(mNewLayers);
         mNewLayers.clear();
+        update.layerCreationArgs = std::move(mNewLayerArgs);
+        mNewLayerArgs.clear();
         update.destroyedHandles = std::move(mDestroyedHandles);
         mDestroyedHandles.clear();
     }
     return update;
 }
 
+void SurfaceFlinger::addToLayerTracing(bool visibleRegionDirty, int64_t time, int64_t vsyncId) {
+    const uint32_t tracingFlags = mLayerTracing.getFlags();
+    LayersProto layers(dumpDrawingStateProto(tracingFlags));
+    if (tracingFlags & LayerTracing::TRACE_EXTRA) {
+        dumpOffscreenLayersProto(layers);
+    }
+    std::string hwcDump;
+    if (tracingFlags & LayerTracing::TRACE_HWC) {
+        dumpHwc(hwcDump);
+    }
+    auto displays = dumpDisplayProto();
+    mLayerTracing.notify(visibleRegionDirty, time, vsyncId, &layers, std::move(hwcDump), &displays);
+}
+
 // gui::ISurfaceComposer
 
 binder::Status SurfaceComposerAIDL::bootFinished() {
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index b353a48..42d5db7 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -455,26 +455,6 @@
         FINISHED,
     };
 
-    struct LayerCreatedState {
-        LayerCreatedState(const wp<Layer>& layer, const wp<Layer>& parent, bool addToRoot)
-              : layer(layer), initialParent(parent), addToRoot(addToRoot) {}
-        wp<Layer> layer;
-        // Indicates the initial parent of the created layer, only used for creating layer in
-        // SurfaceFlinger. If nullptr, it may add the created layer into the current root layers.
-        wp<Layer> initialParent;
-        // Indicates whether the layer getting created should be added at root if there's no parent
-        // and has permission ACCESS_SURFACE_FLINGER. If set to false and no parent, the layer will
-        // be added offscreen.
-        bool addToRoot;
-    };
-
-    struct LifecycleUpdate {
-        std::vector<TransactionState> transactions;
-        std::vector<LayerCreatedState> layerCreatedStates;
-        std::vector<std::unique_ptr<frontend::RequestedLayerState>> newLayers;
-        std::vector<uint32_t> destroyedHandles;
-    };
-
     template <typename F, std::enable_if_t<!std::is_member_function_pointer_v<F>>* = nullptr>
     static Dumper dumper(F&& dump) {
         using namespace std::placeholders;
@@ -721,12 +701,13 @@
             int64_t vsyncId);
     void moveSnapshotsFromCompositionArgs(compositionengine::CompositionRefreshArgs& refreshArgs,
                                           std::vector<std::pair<Layer*, LayerFE*>>& layers);
-    bool updateLayerSnapshotsLegacy(VsyncId vsyncId, LifecycleUpdate& update,
+    bool updateLayerSnapshotsLegacy(VsyncId vsyncId, frontend::Update& update,
                                     bool transactionsFlushed, bool& out)
             REQUIRES(kMainThreadContext);
-    bool updateLayerSnapshots(VsyncId vsyncId, LifecycleUpdate& update, bool transactionsFlushed,
+    bool updateLayerSnapshots(VsyncId vsyncId, frontend::Update& update, bool transactionsFlushed,
                               bool& out) REQUIRES(kMainThreadContext);
-    LifecycleUpdate flushLifecycleUpdates() REQUIRES(kMainThreadContext);
+    void updateLayerHistory(const frontend::LayerSnapshot& snapshot);
+    frontend::Update flushLifecycleUpdates() REQUIRES(kMainThreadContext);
 
     void updateInputFlinger();
     void persistDisplayBrightness(bool needsComposite) REQUIRES(kMainThreadContext);
@@ -736,6 +717,8 @@
     void updateCursorAsync();
 
     void initScheduler(const sp<const DisplayDevice>&) REQUIRES(kMainThreadContext, mStateLock);
+
+    void resetPhaseConfiguration(Fps) REQUIRES(mStateLock, kMainThreadContext);
     void updatePhaseConfiguration(Fps) REQUIRES(mStateLock);
 
     /*
@@ -817,7 +800,7 @@
     void markLayerPendingRemovalLocked(const sp<Layer>& layer);
 
     // add a layer to SurfaceFlinger
-    status_t addClientLayer(const LayerCreationArgs& args, const sp<IBinder>& handle,
+    status_t addClientLayer(LayerCreationArgs& args, const sp<IBinder>& handle,
                             const sp<Layer>& layer, const wp<Layer>& parentLayer,
                             uint32_t* outTransformHint);
 
@@ -854,8 +837,7 @@
      */
 
     // Called during boot, and restart after system_server death.
-    void initializeDisplays();
-    void onInitializeDisplays() REQUIRES(mStateLock, kMainThreadContext);
+    void initializeDisplays() REQUIRES(kMainThreadContext);
 
     sp<const DisplayDevice> getDisplayDeviceLocked(const wp<IBinder>& displayToken) const
             REQUIRES(mStateLock) {
@@ -1084,7 +1066,9 @@
     LayersProto dumpDrawingStateProto(uint32_t traceFlags) const;
     void dumpOffscreenLayersProto(LayersProto& layersProto,
                                   uint32_t traceFlags = LayerTracing::TRACE_ALL) const;
-    void dumpDisplayProto(LayersTraceProto& layersTraceProto) const;
+    google::protobuf::RepeatedPtrField<DisplayProto> dumpDisplayProto() const;
+    void addToLayerTracing(bool visibleRegionDirty, int64_t time, int64_t vsyncId)
+            REQUIRES(kMainThreadContext);
 
     // Dumps state from HW Composer
     void dumpHwc(std::string& result) const;
@@ -1120,9 +1104,6 @@
                                                std::chrono::nanoseconds presentLatency);
     int getMaxAcquiredBufferCountForRefreshRate(Fps refreshRate) const;
 
-    void updateActiveDisplayVsyncLocked(const DisplayDevice& activeDisplay)
-            REQUIRES(mStateLock, kMainThreadContext);
-
     bool isHdrLayer(const frontend::LayerSnapshot& snapshot) const;
 
     ui::Rotation getPhysicalDisplayOrientation(DisplayId, bool isPrimary) const
@@ -1241,7 +1222,7 @@
     bool mLayerCachingEnabled = false;
     bool mBackpressureGpuComposition = false;
 
-    LayerTracing mLayerTracing{*this};
+    LayerTracing mLayerTracing;
     bool mLayerTracingEnabled = false;
 
     std::optional<TransactionTracing> mTransactionTracing;
@@ -1423,6 +1404,7 @@
 
     std::vector<uint32_t> mDestroyedHandles;
     std::vector<std::unique_ptr<frontend::RequestedLayerState>> mNewLayers;
+    std::vector<LayerCreationArgs> mNewLayerArgs;
     // These classes do not store any client state but help with managing transaction callbacks
     // and stats.
     std::unordered_map<uint32_t, sp<Layer>> mLegacyLayers;
diff --git a/services/surfaceflinger/Tracing/LayerTracing.cpp b/services/surfaceflinger/Tracing/LayerTracing.cpp
index 566d553..ecdeabe 100644
--- a/services/surfaceflinger/Tracing/LayerTracing.cpp
+++ b/services/surfaceflinger/Tracing/LayerTracing.cpp
@@ -18,6 +18,8 @@
 #define LOG_TAG "LayerTracing"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
+#include <filesystem>
+
 #include <SurfaceFlinger.h>
 #include <android-base/stringprintf.h>
 #include <log/log.h>
@@ -29,9 +31,8 @@
 
 namespace android {
 
-LayerTracing::LayerTracing(SurfaceFlinger& flinger) : mFlinger(flinger) {
-    mBuffer = std::make_unique<RingBuffer<LayersTraceFileProto, LayersTraceProto>>();
-}
+LayerTracing::LayerTracing()
+      : mBuffer(std::make_unique<RingBuffer<LayersTraceFileProto, LayersTraceProto>>()) {}
 
 LayerTracing::~LayerTracing() = default;
 
@@ -45,30 +46,39 @@
     return true;
 }
 
-bool LayerTracing::disable(std::string filename) {
+bool LayerTracing::disable(std::string filename, bool writeToFile) {
     std::scoped_lock lock(mTraceLock);
     if (!mEnabled) {
         return false;
     }
     mEnabled = false;
-    LayersTraceFileProto fileProto = createTraceFileProto();
-    mBuffer->writeToFile(fileProto, filename);
+    if (writeToFile) {
+        LayersTraceFileProto fileProto = createTraceFileProto();
+        mBuffer->writeToFile(fileProto, filename);
+    }
     mBuffer->reset();
     return true;
 }
 
+void LayerTracing::appendToStream(std::ofstream& out) {
+    std::scoped_lock lock(mTraceLock);
+    LayersTraceFileProto fileProto = createTraceFileProto();
+    mBuffer->appendToStream(fileProto, out);
+    mBuffer->reset();
+}
+
 bool LayerTracing::isEnabled() const {
     std::scoped_lock lock(mTraceLock);
     return mEnabled;
 }
 
-status_t LayerTracing::writeToFile() {
+status_t LayerTracing::writeToFile(std::string filename) {
     std::scoped_lock lock(mTraceLock);
     if (!mEnabled) {
         return STATUS_OK;
     }
     LayersTraceFileProto fileProto = createTraceFileProto();
-    return mBuffer->writeToFile(fileProto, FILE_NAME);
+    return mBuffer->writeToFile(fileProto, filename);
 }
 
 void LayerTracing::setTraceFlags(uint32_t flags) {
@@ -84,8 +94,11 @@
 bool LayerTracing::flagIsSet(uint32_t flags) const {
     return (mFlags & flags) == flags;
 }
+uint32_t LayerTracing::getFlags() const {
+    return mFlags;
+}
 
-LayersTraceFileProto LayerTracing::createTraceFileProto() const {
+LayersTraceFileProto LayerTracing::createTraceFileProto() {
     LayersTraceFileProto fileProto;
     fileProto.set_magic_number(uint64_t(LayersTraceFileProto_MagicNumber_MAGIC_NUMBER_H) << 32 |
                                LayersTraceFileProto_MagicNumber_MAGIC_NUMBER_L);
@@ -101,7 +114,9 @@
     mBuffer->dump(result);
 }
 
-void LayerTracing::notify(bool visibleRegionDirty, int64_t time, int64_t vsyncId) {
+void LayerTracing::notify(bool visibleRegionDirty, int64_t time, int64_t vsyncId,
+                          LayersProto* layers, std::string hwcDump,
+                          google::protobuf::RepeatedPtrField<DisplayProto>* displays) {
     std::scoped_lock lock(mTraceLock);
     if (!mEnabled) {
         return;
@@ -116,22 +131,15 @@
     entry.set_elapsed_realtime_nanos(time);
     const char* where = visibleRegionDirty ? "visibleRegionsDirty" : "bufferLatched";
     entry.set_where(where);
-    LayersProto layers(mFlinger.dumpDrawingStateProto(mFlags));
-
-    if (flagIsSet(LayerTracing::TRACE_EXTRA)) {
-        mFlinger.dumpOffscreenLayersProto(layers);
-    }
-    entry.mutable_layers()->Swap(&layers);
+    entry.mutable_layers()->Swap(layers);
 
     if (flagIsSet(LayerTracing::TRACE_HWC)) {
-        std::string hwcDump;
-        mFlinger.dumpHwc(hwcDump);
         entry.set_hwc_blob(hwcDump);
     }
     if (!flagIsSet(LayerTracing::TRACE_COMPOSITION)) {
         entry.set_excludes_composition_state(true);
     }
-    mFlinger.dumpDisplayProto(entry);
+    entry.mutable_displays()->Swap(displays);
     entry.set_vsync_id(vsyncId);
     mBuffer->emplace(std::move(entry));
 }
diff --git a/services/surfaceflinger/Tracing/LayerTracing.h b/services/surfaceflinger/Tracing/LayerTracing.h
index b32001c..40b0fbe 100644
--- a/services/surfaceflinger/Tracing/LayerTracing.h
+++ b/services/surfaceflinger/Tracing/LayerTracing.h
@@ -40,14 +40,16 @@
  */
 class LayerTracing {
 public:
-    LayerTracing(SurfaceFlinger& flinger);
+    LayerTracing();
     ~LayerTracing();
     bool enable();
-    bool disable(std::string filename = FILE_NAME);
+    bool disable(std::string filename = FILE_NAME, bool writeToFile = true);
+    void appendToStream(std::ofstream& out);
     bool isEnabled() const;
-    status_t writeToFile();
-    LayersTraceFileProto createTraceFileProto() const;
-    void notify(bool visibleRegionDirty, int64_t time, int64_t vsyncId);
+    status_t writeToFile(std::string filename = FILE_NAME);
+    static LayersTraceFileProto createTraceFileProto();
+    void notify(bool visibleRegionDirty, int64_t time, int64_t vsyncId, LayersProto* layers,
+                std::string hwcDump, google::protobuf::RepeatedPtrField<DisplayProto>* displays);
 
     enum : uint32_t {
         TRACE_INPUT = 1 << 1,
@@ -60,13 +62,12 @@
     };
     void setTraceFlags(uint32_t flags);
     bool flagIsSet(uint32_t flags) const;
+    uint32_t getFlags() const;
     void setBufferSize(size_t bufferSizeInBytes);
     void dump(std::string&) const;
 
 private:
     static constexpr auto FILE_NAME = "/data/misc/wmtrace/layers_trace.winscope";
-
-    SurfaceFlinger& mFlinger;
     uint32_t mFlags = TRACE_INPUT;
     mutable std::mutex mTraceLock;
     bool mEnabled GUARDED_BY(mTraceLock) = false;
diff --git a/services/surfaceflinger/Tracing/RingBuffer.h b/services/surfaceflinger/Tracing/RingBuffer.h
index 7e38c55..b41c65b 100644
--- a/services/surfaceflinger/Tracing/RingBuffer.h
+++ b/services/surfaceflinger/Tracing/RingBuffer.h
@@ -24,6 +24,7 @@
 #include <utils/Timers.h>
 #include <utils/Trace.h>
 #include <chrono>
+#include <fstream>
 #include <queue>
 
 namespace android {
@@ -73,6 +74,19 @@
         return NO_ERROR;
     }
 
+    status_t appendToStream(FileProto& fileProto, std::ofstream& out) {
+        ATRACE_CALL();
+        writeToProto(fileProto);
+        std::string output;
+        if (!fileProto.SerializeToString(&output)) {
+            ALOGE("Could not serialize proto.");
+            return UNKNOWN_ERROR;
+        }
+
+        out << output;
+        return NO_ERROR;
+    }
+
     std::vector<std::string> emplace(std::string&& serializedProto) {
         std::vector<std::string> replacedEntries;
         size_t protoSize = static_cast<size_t>(serializedProto.size());
diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
index ba08cee..8fd6538 100644
--- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
+++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
@@ -15,15 +15,43 @@
  */
 
 #include <gui/SurfaceComposerClient.h>
-#include <renderengine/mock/FakeExternalTexture.h>
 #include <ui/Fence.h>
 #include <ui/Rect.h>
 
+#include "FrontEnd/LayerCreationArgs.h"
 #include "LayerProtoHelper.h"
 #include "TransactionProtoParser.h"
+#include "TransactionState.h"
+#include "gui/LayerState.h"
 
 namespace android::surfaceflinger {
 
+class FakeExternalTexture : public renderengine::ExternalTexture {
+    const sp<GraphicBuffer> mEmptyBuffer =
+            sp<GraphicBuffer>::make(1u, 1u, PIXEL_FORMAT_RGBA_8888,
+                                    GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_OFTEN);
+    uint32_t mWidth;
+    uint32_t mHeight;
+    uint64_t mId;
+    PixelFormat mPixelFormat;
+    uint64_t mUsage;
+
+public:
+    FakeExternalTexture(uint32_t width, uint32_t height, uint64_t id, PixelFormat pixelFormat,
+                        uint64_t usage)
+          : mWidth(width), mHeight(height), mId(id), mPixelFormat(pixelFormat), mUsage(usage) {}
+    const sp<GraphicBuffer>& getBuffer() const { return mEmptyBuffer; }
+    bool hasSameBuffer(const renderengine::ExternalTexture& other) const override {
+        return getId() == other.getId();
+    }
+    uint32_t getWidth() const override { return mWidth; }
+    uint32_t getHeight() const override { return mHeight; }
+    uint64_t getId() const override { return mId; }
+    PixelFormat getPixelFormat() const override { return mPixelFormat; }
+    uint64_t getUsage() const override { return mUsage; }
+    ~FakeExternalTexture() = default;
+};
+
 proto::TransactionState TransactionProtoParser::toProto(const TransactionState& t) {
     proto::TransactionState proto;
     proto.set_pid(t.originPid);
@@ -35,7 +63,7 @@
 
     proto.mutable_layer_changes()->Reserve(static_cast<int32_t>(t.states.size()));
     for (auto& layerState : t.states) {
-        proto.mutable_layer_changes()->Add(std::move(toProto(layerState.state)));
+        proto.mutable_layer_changes()->Add(std::move(toProto(layerState)));
     }
 
     proto.mutable_display_changes()->Reserve(static_cast<int32_t>(t.displays.size()));
@@ -46,40 +74,22 @@
 }
 
 proto::TransactionState TransactionProtoParser::toProto(
-        const std::map<int32_t /* layerId */, TracingLayerState>& states) {
+        const std::map<uint32_t /* layerId */, TracingLayerState>& states) {
     proto::TransactionState proto;
     proto.mutable_layer_changes()->Reserve(static_cast<int32_t>(states.size()));
     for (auto& [layerId, state] : states) {
         proto::LayerState layerProto = toProto(state);
-        if (layerProto.has_buffer_data()) {
-            proto::LayerState_BufferData* bufferProto = layerProto.mutable_buffer_data();
-            bufferProto->set_buffer_id(state.bufferId);
-            bufferProto->set_width(state.bufferWidth);
-            bufferProto->set_height(state.bufferHeight);
-            bufferProto->set_pixel_format(
-                    static_cast<proto::LayerState_BufferData_PixelFormat>(state.pixelFormat));
-            bufferProto->set_usage(state.bufferUsage);
-        }
         layerProto.set_has_sideband_stream(state.hasSidebandStream);
-        layerProto.set_layer_id(state.layerId);
-        layerProto.set_parent_id(state.parentId);
-        layerProto.set_relative_parent_id(state.relativeParentId);
-        if (layerProto.has_window_info_handle()) {
-            layerProto.mutable_window_info_handle()->set_crop_layer_id(state.inputCropId);
-        }
         proto.mutable_layer_changes()->Add(std::move(layerProto));
     }
     return proto;
 }
 
-proto::LayerState TransactionProtoParser::toProto(const layer_state_t& layer) {
+proto::LayerState TransactionProtoParser::toProto(
+        const ResolvedComposerState& resolvedComposerState) {
     proto::LayerState proto;
-    if (layer.surface) {
-        proto.set_layer_id(mMapper->getLayerId(layer.surface));
-    } else {
-        proto.set_layer_id(layer.layerId);
-    }
-
+    auto& layer = resolvedComposerState.state;
+    proto.set_layer_id(resolvedComposerState.layerId);
     proto.set_what(layer.what);
 
     if (layer.what & layer_state_t::ePositionChanged) {
@@ -135,27 +145,13 @@
     }
     if (layer.what & layer_state_t::eBufferChanged) {
         proto::LayerState_BufferData* bufferProto = proto.mutable_buffer_data();
-        if (layer.bufferData->hasBuffer()) {
-            bufferProto->set_buffer_id(layer.bufferData->getId());
-            bufferProto->set_width(layer.bufferData->getWidth());
-            bufferProto->set_height(layer.bufferData->getHeight());
+        if (resolvedComposerState.externalTexture) {
+            bufferProto->set_buffer_id(resolvedComposerState.externalTexture->getId());
+            bufferProto->set_width(resolvedComposerState.externalTexture->getWidth());
+            bufferProto->set_height(resolvedComposerState.externalTexture->getHeight());
             bufferProto->set_pixel_format(static_cast<proto::LayerState_BufferData_PixelFormat>(
-                    layer.bufferData->getPixelFormat()));
-            bufferProto->set_usage(layer.bufferData->getUsage());
-        } else {
-            uint64_t bufferId;
-            uint32_t width;
-            uint32_t height;
-            int32_t pixelFormat;
-            uint64_t usage;
-            mMapper->getGraphicBufferPropertiesFromCache(layer.bufferData->cachedBuffer, &bufferId,
-                                                         &width, &height, &pixelFormat, &usage);
-            bufferProto->set_buffer_id(bufferId);
-            bufferProto->set_width(width);
-            bufferProto->set_height(height);
-            bufferProto->set_pixel_format(
-                    static_cast<proto::LayerState_BufferData_PixelFormat>(pixelFormat));
-            bufferProto->set_usage(usage);
+                    resolvedComposerState.externalTexture->getPixelFormat()));
+            bufferProto->set_usage(resolvedComposerState.externalTexture->getUsage());
         }
         bufferProto->set_frame_number(layer.bufferData->frameNumber);
         bufferProto->set_flags(layer.bufferData->flags.get());
@@ -179,16 +175,10 @@
     }
 
     if (layer.what & layer_state_t::eReparent) {
-        int64_t layerId = layer.parentSurfaceControlForChild
-                ? mMapper->getLayerId(layer.parentSurfaceControlForChild->getHandle())
-                : -1;
-        proto.set_parent_id(layerId);
+        proto.set_parent_id(resolvedComposerState.parentId);
     }
     if (layer.what & layer_state_t::eRelativeLayerChanged) {
-        int64_t layerId = layer.relativeLayerSurfaceControl
-                ? mMapper->getLayerId(layer.relativeLayerSurfaceControl->getHandle())
-                : -1;
-        proto.set_relative_parent_id(layerId);
+        proto.set_relative_parent_id(resolvedComposerState.relativeParentId);
         proto.set_z(layer.z);
     }
 
@@ -207,7 +197,7 @@
             windowInfoProto->set_has_wallpaper(inputInfo->inputConfig.test(
                     gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER));
             windowInfoProto->set_global_scale_factor(inputInfo->globalScaleFactor);
-            proto::LayerState_Transform* transformProto = windowInfoProto->mutable_transform();
+            proto::Transform* transformProto = windowInfoProto->mutable_transform();
             transformProto->set_dsdx(inputInfo->transform.dsdx());
             transformProto->set_dtdx(inputInfo->transform.dtdx());
             transformProto->set_dtdy(inputInfo->transform.dtdy());
@@ -216,8 +206,7 @@
             transformProto->set_ty(inputInfo->transform.ty());
             windowInfoProto->set_replace_touchable_region_with_crop(
                     inputInfo->replaceTouchableRegionWithCrop);
-            windowInfoProto->set_crop_layer_id(
-                    mMapper->getLayerId(inputInfo->touchableRegionCropHandle.promote()));
+            windowInfoProto->set_crop_layer_id(resolvedComposerState.touchCropId);
         }
     }
     if (layer.what & layer_state_t::eBackgroundColorChanged) {
@@ -289,13 +278,15 @@
     return proto;
 }
 
-proto::LayerCreationArgs TransactionProtoParser::toProto(const TracingLayerCreationArgs& args) {
+proto::LayerCreationArgs TransactionProtoParser::toProto(const LayerCreationArgs& args) {
     proto::LayerCreationArgs proto;
-    proto.set_layer_id(args.layerId);
+    proto.set_layer_id(args.sequence);
     proto.set_name(args.name);
     proto.set_flags(args.flags);
     proto.set_parent_id(args.parentId);
-    proto.set_mirror_from_id(args.mirrorFromId);
+    proto.set_mirror_from_id(args.layerIdToMirror);
+    proto.set_add_to_root(args.addToRoot);
+    proto.set_layer_stack_to_mirror(args.layerStackToMirror.id);
     return proto;
 }
 
@@ -313,15 +304,7 @@
     for (int i = 0; i < layerCount; i++) {
         ResolvedComposerState s;
         s.state.what = 0;
-        fromProto(proto.layer_changes(i), s.state);
-        if (s.state.bufferData) {
-            s.externalTexture = std::make_shared<
-                    renderengine::mock::FakeExternalTexture>(s.state.bufferData->getWidth(),
-                                                             s.state.bufferData->getHeight(),
-                                                             s.state.bufferData->getId(),
-                                                             s.state.bufferData->getPixelFormat(),
-                                                             s.state.bufferData->getUsage());
-        }
+        fromProto(proto.layer_changes(i), s);
         t.states.emplace_back(s);
     }
 
@@ -334,46 +317,47 @@
 }
 
 void TransactionProtoParser::fromProto(const proto::LayerCreationArgs& proto,
-                                       TracingLayerCreationArgs& outArgs) {
-    outArgs.layerId = proto.layer_id();
+                                       LayerCreationArgs& outArgs) {
+    outArgs.sequence = proto.layer_id();
+
     outArgs.name = proto.name();
     outArgs.flags = proto.flags();
     outArgs.parentId = proto.parent_id();
-    outArgs.mirrorFromId = proto.mirror_from_id();
+    outArgs.layerIdToMirror = proto.mirror_from_id();
+    outArgs.addToRoot = proto.add_to_root();
+    outArgs.layerStackToMirror.id = proto.layer_stack_to_mirror();
 }
 
 void TransactionProtoParser::mergeFromProto(const proto::LayerState& proto,
                                             TracingLayerState& outState) {
-    layer_state_t state;
-    fromProto(proto, state);
-    outState.merge(state);
+    ResolvedComposerState resolvedComposerState;
+    fromProto(proto, resolvedComposerState);
+    layer_state_t& state = resolvedComposerState.state;
+    outState.state.merge(state);
+    outState.layerId = resolvedComposerState.layerId;
 
     if (state.what & layer_state_t::eReparent) {
-        outState.parentId = static_cast<int32_t>(proto.parent_id());
+        outState.parentId = resolvedComposerState.parentId;
     }
     if (state.what & layer_state_t::eRelativeLayerChanged) {
-        outState.relativeParentId = static_cast<int32_t>(proto.relative_parent_id());
+        outState.relativeParentId = resolvedComposerState.relativeParentId;
     }
     if (state.what & layer_state_t::eInputInfoChanged) {
-        outState.inputCropId = static_cast<int32_t>(proto.window_info_handle().crop_layer_id());
+        outState.touchCropId = resolvedComposerState.touchCropId;
     }
     if (state.what & layer_state_t::eBufferChanged) {
-        const proto::LayerState_BufferData& bufferProto = proto.buffer_data();
-        outState.bufferId = bufferProto.buffer_id();
-        outState.bufferWidth = bufferProto.width();
-        outState.bufferHeight = bufferProto.height();
-        outState.pixelFormat = bufferProto.pixel_format();
-        outState.bufferUsage = bufferProto.usage();
+        outState.externalTexture = resolvedComposerState.externalTexture;
     }
     if (state.what & layer_state_t::eSidebandStreamChanged) {
         outState.hasSidebandStream = proto.has_sideband_stream();
     }
 }
 
-void TransactionProtoParser::fromProto(const proto::LayerState& proto, layer_state_t& layer) {
-    layer.layerId = (int32_t)proto.layer_id();
+void TransactionProtoParser::fromProto(const proto::LayerState& proto,
+                                       ResolvedComposerState& resolvedComposerState) {
+    auto& layer = resolvedComposerState.state;
+    resolvedComposerState.layerId = proto.layer_id();
     layer.what |= proto.what();
-    layer.surface = mMapper->getLayerHandle(layer.layerId);
 
     if (proto.what() & layer_state_t::ePositionChanged) {
         layer.x = proto.x();
@@ -428,9 +412,15 @@
     if (proto.what() & layer_state_t::eBufferChanged) {
         const proto::LayerState_BufferData& bufferProto = proto.buffer_data();
         layer.bufferData =
-                std::move(mMapper->getGraphicData(bufferProto.buffer_id(), bufferProto.width(),
-                                                  bufferProto.height(), bufferProto.pixel_format(),
-                                                  bufferProto.usage()));
+                std::make_shared<fake::BufferData>(bufferProto.buffer_id(), bufferProto.width(),
+                                                   bufferProto.height(), bufferProto.pixel_format(),
+                                                   bufferProto.usage());
+        resolvedComposerState.externalTexture =
+                std::make_shared<FakeExternalTexture>(layer.bufferData->getWidth(),
+                                                      layer.bufferData->getHeight(),
+                                                      layer.bufferData->getId(),
+                                                      layer.bufferData->getPixelFormat(),
+                                                      layer.bufferData->getUsage());
         layer.bufferData->frameNumber = bufferProto.frame_number();
         layer.bufferData->flags = ftl::Flags<BufferData::BufferDataChange>(bufferProto.flags());
         layer.bufferData->cachedBuffer.id = bufferProto.cached_buffer_id();
@@ -454,26 +444,10 @@
     }
 
     if (proto.what() & layer_state_t::eReparent) {
-        int64_t layerId = proto.parent_id();
-        if (layerId == -1) {
-            layer.parentSurfaceControlForChild = nullptr;
-        } else {
-            layer.parentSurfaceControlForChild =
-                    sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(),
-                                             mMapper->getLayerHandle(static_cast<int32_t>(layerId)),
-                                             static_cast<int32_t>(layerId), "");
-        }
+        resolvedComposerState.parentId = proto.parent_id();
     }
     if (proto.what() & layer_state_t::eRelativeLayerChanged) {
-        int64_t layerId = proto.relative_parent_id();
-        if (layerId == -1) {
-            layer.relativeLayerSurfaceControl = nullptr;
-        } else {
-            layer.relativeLayerSurfaceControl =
-                    sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(),
-                                             mMapper->getLayerHandle(static_cast<int32_t>(layerId)),
-                                             static_cast<int32_t>(layerId), "");
-        }
+        resolvedComposerState.relativeParentId = proto.relative_parent_id();
         layer.z = proto.z();
     }
 
@@ -493,19 +467,13 @@
         inputInfo.setInputConfig(gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER,
                                  windowInfoProto.has_wallpaper());
         inputInfo.globalScaleFactor = windowInfoProto.global_scale_factor();
-        const proto::LayerState_Transform& transformProto = windowInfoProto.transform();
+        const proto::Transform& transformProto = windowInfoProto.transform();
         inputInfo.transform.set(transformProto.dsdx(), transformProto.dtdx(), transformProto.dtdy(),
                                 transformProto.dsdy());
         inputInfo.transform.set(transformProto.tx(), transformProto.ty());
         inputInfo.replaceTouchableRegionWithCrop =
                 windowInfoProto.replace_touchable_region_with_crop();
-        int64_t layerId = windowInfoProto.crop_layer_id();
-        if (layerId != -1) {
-            inputInfo.touchableRegionCropHandle =
-                    mMapper->getLayerHandle(static_cast<int32_t>(layerId));
-        } else {
-            inputInfo.touchableRegionCropHandle = wp<IBinder>();
-        }
+        resolvedComposerState.touchCropId = windowInfoProto.crop_layer_id();
 
         layer.windowInfoHandle = sp<gui::WindowInfoHandle>::make(inputInfo);
     }
@@ -577,4 +545,62 @@
     return display;
 }
 
+void asProto(proto::Transform* proto, const ui::Transform& transform) {
+    proto->set_dsdx(transform.dsdx());
+    proto->set_dtdx(transform.dtdx());
+    proto->set_dtdy(transform.dtdy());
+    proto->set_dsdy(transform.dsdy());
+    proto->set_tx(transform.tx());
+    proto->set_ty(transform.ty());
+}
+
+proto::DisplayInfo TransactionProtoParser::toProto(const frontend::DisplayInfo& displayInfo,
+                                                   uint32_t layerStack) {
+    proto::DisplayInfo proto;
+    proto.set_layer_stack(layerStack);
+    proto.set_display_id(displayInfo.info.displayId);
+    proto.set_logical_width(displayInfo.info.logicalWidth);
+    proto.set_logical_height(displayInfo.info.logicalHeight);
+    asProto(proto.mutable_transform_inverse(), displayInfo.info.transform);
+    asProto(proto.mutable_transform(), displayInfo.transform);
+    proto.set_receives_input(displayInfo.receivesInput);
+    proto.set_is_secure(displayInfo.isSecure);
+    proto.set_is_primary(displayInfo.isPrimary);
+    proto.set_is_virtual(displayInfo.isVirtual);
+    proto.set_rotation_flags((int)displayInfo.rotationFlags);
+    proto.set_transform_hint((int)displayInfo.transformHint);
+    return proto;
+}
+
+void fromProto2(ui::Transform& outTransform, const proto::Transform& proto) {
+    outTransform.set(proto.dsdx(), proto.dtdx(), proto.dtdy(), proto.dsdy());
+    outTransform.set(proto.tx(), proto.ty());
+}
+
+frontend::DisplayInfo TransactionProtoParser::fromProto(const proto::DisplayInfo& proto) {
+    frontend::DisplayInfo displayInfo;
+    displayInfo.info.displayId = proto.display_id();
+    displayInfo.info.logicalWidth = proto.logical_width();
+    displayInfo.info.logicalHeight = proto.logical_height();
+    fromProto2(displayInfo.info.transform, proto.transform_inverse());
+    fromProto2(displayInfo.transform, proto.transform());
+    displayInfo.receivesInput = proto.receives_input();
+    displayInfo.isSecure = proto.is_secure();
+    displayInfo.isPrimary = proto.is_primary();
+    displayInfo.isPrimary = proto.is_virtual();
+    displayInfo.rotationFlags = (ui::Transform::RotationFlags)proto.rotation_flags();
+    displayInfo.transformHint = (ui::Transform::RotationFlags)proto.transform_hint();
+    return displayInfo;
+}
+
+void TransactionProtoParser::fromProto(
+        const google::protobuf::RepeatedPtrField<proto::DisplayInfo>& proto,
+        display::DisplayMap<ui::LayerStack, frontend::DisplayInfo> outDisplayInfos) {
+    outDisplayInfos.clear();
+    for (const proto::DisplayInfo& displayInfo : proto) {
+        outDisplayInfos.emplace_or_replace(ui::LayerStack::fromValue(displayInfo.layer_stack()),
+                                           fromProto(displayInfo));
+    }
+}
+
 } // namespace android::surfaceflinger
diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.h b/services/surfaceflinger/Tracing/TransactionProtoParser.h
index 2232bb9..50944fc 100644
--- a/services/surfaceflinger/Tracing/TransactionProtoParser.h
+++ b/services/surfaceflinger/Tracing/TransactionProtoParser.h
@@ -18,30 +18,17 @@
 #include <gui/fake/BufferData.h>
 #include <layerproto/TransactionProto.h>
 #include <utils/RefBase.h>
+#include "Display/DisplayMap.h"
+#include "FrontEnd/DisplayInfo.h"
 
+#include "FrontEnd/LayerCreationArgs.h"
 #include "TransactionState.h"
 
 namespace android::surfaceflinger {
 
-struct TracingLayerCreationArgs {
-    int32_t layerId;
-    std::string name;
-    uint32_t flags = 0;
-    int32_t parentId = -1;
-    int32_t mirrorFromId = -1;
-};
-
-struct TracingLayerState : layer_state_t {
-    uint64_t bufferId;
-    uint32_t bufferHeight;
-    uint32_t bufferWidth;
-    int32_t pixelFormat;
-    uint64_t bufferUsage;
+struct TracingLayerState : ResolvedComposerState {
     bool hasSidebandStream;
-    int32_t parentId;
-    int32_t relativeParentId;
-    int32_t inputCropId;
-    TracingLayerCreationArgs args;
+    LayerCreationArgs args;
 };
 
 class TransactionProtoParser {
@@ -51,40 +38,30 @@
     class FlingerDataMapper {
     public:
         virtual ~FlingerDataMapper() = default;
-        virtual sp<IBinder> getLayerHandle(int32_t /* layerId */) const { return nullptr; }
-        virtual int64_t getLayerId(const sp<IBinder>& /* layerHandle */) const { return -1; }
-        virtual int64_t getLayerId(BBinder* /* layerHandle */) const { return -1; }
         virtual sp<IBinder> getDisplayHandle(int32_t /* displayId */) const { return nullptr; }
         virtual int32_t getDisplayId(const sp<IBinder>& /* displayHandle */) const { return -1; }
-        virtual std::shared_ptr<BufferData> getGraphicData(uint64_t bufferId, uint32_t width,
-                                                           uint32_t height, int32_t pixelFormat,
-                                                           uint64_t usage) const {
-            return std::make_shared<fake::BufferData>(bufferId, width, height, pixelFormat, usage);
-        }
-        virtual void getGraphicBufferPropertiesFromCache(client_cache_t /* cachedBuffer */,
-                                                         uint64_t* /* outBufferId */,
-                                                         uint32_t* /* outWidth */,
-                                                         uint32_t* /* outHeight */,
-                                                         int32_t* /* outPixelFormat */,
-                                                         uint64_t* /* outUsage */) const {}
     };
 
     TransactionProtoParser(std::unique_ptr<FlingerDataMapper> provider)
           : mMapper(std::move(provider)) {}
 
     proto::TransactionState toProto(const TransactionState&);
-    proto::TransactionState toProto(const std::map<int32_t /* layerId */, TracingLayerState>&);
-    proto::LayerCreationArgs toProto(const TracingLayerCreationArgs& args);
+    proto::TransactionState toProto(const std::map<uint32_t /* layerId */, TracingLayerState>&);
+    proto::LayerCreationArgs toProto(const LayerCreationArgs& args);
+    proto::LayerState toProto(const ResolvedComposerState&);
+    proto::DisplayInfo toProto(const frontend::DisplayInfo&, uint32_t layerStack);
 
     TransactionState fromProto(const proto::TransactionState&);
     void mergeFromProto(const proto::LayerState&, TracingLayerState& outState);
-    void fromProto(const proto::LayerCreationArgs&, TracingLayerCreationArgs& outArgs);
+    void fromProto(const proto::LayerCreationArgs&, LayerCreationArgs& outArgs);
     std::unique_ptr<FlingerDataMapper> mMapper;
+    frontend::DisplayInfo fromProto(const proto::DisplayInfo&);
+    void fromProto(const google::protobuf::RepeatedPtrField<proto::DisplayInfo>&,
+                   display::DisplayMap<ui::LayerStack, frontend::DisplayInfo> outDisplayInfos);
 
 private:
-    proto::LayerState toProto(const layer_state_t&);
     proto::DisplayState toProto(const DisplayState&);
-    void fromProto(const proto::LayerState&, layer_state_t& out);
+    void fromProto(const proto::LayerState&, ResolvedComposerState& out);
     DisplayState fromProto(const proto::DisplayState&);
 
 };
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.cpp b/services/surfaceflinger/Tracing/TransactionTracing.cpp
index cb5320b..26ed878 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.cpp
+++ b/services/surfaceflinger/Tracing/TransactionTracing.cpp
@@ -23,75 +23,14 @@
 #include <utils/SystemClock.h>
 #include <utils/Trace.h>
 
-#include "ClientCache.h"
+#include "Client.h"
+#include "FrontEnd/LayerCreationArgs.h"
 #include "TransactionTracing.h"
-#include "renderengine/ExternalTexture.h"
 
 namespace android {
 
-// Keeps the binder address as the layer id so we can avoid holding the tracing lock in the
-// binder thread.
-class FlatDataMapper : public TransactionProtoParser::FlingerDataMapper {
-public:
-    virtual int64_t getLayerId(const sp<IBinder>& layerHandle) const {
-        if (layerHandle == nullptr) {
-            return -1;
-        }
-
-        return reinterpret_cast<int64_t>(layerHandle->localBinder());
-    }
-
-    void getGraphicBufferPropertiesFromCache(client_cache_t cachedBuffer, uint64_t* outBufferId,
-                                             uint32_t* outWidth, uint32_t* outHeight,
-                                             int32_t* outPixelFormat,
-                                             uint64_t* outUsage) const override {
-        std::shared_ptr<renderengine::ExternalTexture> buffer =
-                ClientCache::getInstance().get(cachedBuffer);
-        if (!buffer || !buffer->getBuffer()) {
-            *outBufferId = 0;
-            *outWidth = 0;
-            *outHeight = 0;
-            *outPixelFormat = 0;
-            *outUsage = 0;
-            return;
-        }
-
-        *outBufferId = buffer->getId();
-        *outWidth = buffer->getWidth();
-        *outHeight = buffer->getHeight();
-        *outPixelFormat = buffer->getPixelFormat();
-        *outUsage = buffer->getUsage();
-        return;
-    }
-};
-
-class FlingerDataMapper : public FlatDataMapper {
-    std::unordered_map<BBinder* /* layerHandle */, int32_t /* layerId */>& mLayerHandles;
-
-public:
-    FlingerDataMapper(std::unordered_map<BBinder* /* handle */, int32_t /* id */>& layerHandles)
-          : mLayerHandles(layerHandles) {}
-
-    int64_t getLayerId(const sp<IBinder>& layerHandle) const override {
-        if (layerHandle == nullptr) {
-            return -1;
-        }
-        return getLayerId(layerHandle->localBinder());
-    }
-
-    int64_t getLayerId(BBinder* localBinder) const {
-        auto it = mLayerHandles.find(localBinder);
-        if (it == mLayerHandles.end()) {
-            ALOGW("Could not find layer handle %p", localBinder);
-            return -1;
-        }
-        return it->second;
-    }
-};
-
 TransactionTracing::TransactionTracing()
-      : mProtoParser(std::make_unique<FlingerDataMapper>(mLayerHandles)),
-        mLockfreeProtoParser(std::make_unique<FlatDataMapper>()) {
+      : mProtoParser(std::make_unique<TransactionProtoParser::FlingerDataMapper>()) {
     std::scoped_lock lock(mTraceLock);
 
     mBuffer.setSize(mBufferSizeInBytes);
@@ -137,84 +76,77 @@
     auto timeOffsetNs = static_cast<std::uint64_t>(systemTime(SYSTEM_TIME_REALTIME) -
                                                    systemTime(SYSTEM_TIME_MONOTONIC));
     proto.set_real_to_elapsed_time_offset_nanos(timeOffsetNs);
+    proto.set_version(TRACING_VERSION);
     return proto;
 }
 
 void TransactionTracing::dump(std::string& result) const {
     std::scoped_lock lock(mTraceLock);
-    base::StringAppendF(&result,
-                        "  queued transactions=%zu created layers=%zu handles=%zu states=%zu\n",
-                        mQueuedTransactions.size(), mCreatedLayers.size(), mLayerHandles.size(),
-                        mStartingStates.size());
+    base::StringAppendF(&result, "  queued transactions=%zu created layers=%zu states=%zu\n",
+                        mQueuedTransactions.size(), mCreatedLayers.size(), mStartingStates.size());
     mBuffer.dump(result);
 }
 
 void TransactionTracing::addQueuedTransaction(const TransactionState& transaction) {
-    proto::TransactionState* state =
-            new proto::TransactionState(mLockfreeProtoParser.toProto(transaction));
+    proto::TransactionState* state = new proto::TransactionState(mProtoParser.toProto(transaction));
     mTransactionQueue.push(state);
 }
 
-TransactionTracing::CommittedTransactions&
-TransactionTracing::findOrCreateCommittedTransactionRecord(int64_t vsyncId) {
-    for (auto& pendingTransaction : mPendingTransactions) {
-        if (pendingTransaction.vsyncId == vsyncId) {
-            return pendingTransaction;
-        }
+void TransactionTracing::addCommittedTransactions(
+        int64_t vsyncId, nsecs_t commitTime, frontend::Update& newUpdate,
+        const display::DisplayMap<ui::LayerStack, frontend::DisplayInfo>& displayInfos,
+        bool displayInfoChanged) {
+    CommittedUpdates update;
+    update.vsyncId = vsyncId;
+    update.timestamp = commitTime;
+    update.transactionIds.reserve(newUpdate.transactions.size());
+    for (const auto& transaction : newUpdate.transactions) {
+        update.transactionIds.emplace_back(transaction.id);
     }
-
-    CommittedTransactions committedTransactions;
-    committedTransactions.vsyncId = vsyncId;
-    committedTransactions.timestamp = systemTime();
-    mPendingTransactions.emplace_back(committedTransactions);
-    return mPendingTransactions.back();
-}
-
-void TransactionTracing::onLayerAddedToDrawingState(int layerId, int64_t vsyncId) {
-    CommittedTransactions& committedTransactions = findOrCreateCommittedTransactionRecord(vsyncId);
-    committedTransactions.createdLayerIds.emplace_back(layerId);
-}
-
-void TransactionTracing::addCommittedTransactions(std::vector<TransactionState>& transactions,
-                                                  int64_t vsyncId) {
-    CommittedTransactions& committedTransactions = findOrCreateCommittedTransactionRecord(vsyncId);
-    committedTransactions.transactionIds.reserve(transactions.size());
-    for (const auto& transaction : transactions) {
-        committedTransactions.transactionIds.emplace_back(transaction.id);
+    update.displayInfoChanged = displayInfoChanged;
+    if (displayInfoChanged) {
+        update.displayInfos = displayInfos;
     }
+    update.createdLayers = std::move(newUpdate.layerCreationArgs);
+    newUpdate.layerCreationArgs.clear();
+    update.destroyedLayerHandles.reserve(newUpdate.destroyedHandles.size());
+    for (uint32_t handle : newUpdate.destroyedHandles) {
+        update.destroyedLayerHandles.push_back(handle);
+    }
+    mPendingUpdates.emplace_back(update);
     tryPushToTracingThread();
 }
 
 void TransactionTracing::loop() {
     while (true) {
-        std::vector<CommittedTransactions> committedTransactions;
-        std::vector<int32_t> removedLayers;
+        std::vector<CommittedUpdates> committedUpdates;
+        std::vector<uint32_t> destroyedLayers;
         {
             std::unique_lock<std::mutex> lock(mMainThreadLock);
             base::ScopedLockAssertion assumeLocked(mMainThreadLock);
             mTransactionsAvailableCv.wait(lock, [&]() REQUIRES(mMainThreadLock) {
-                return mDone || !mCommittedTransactions.empty();
+                return mDone || !mUpdates.empty();
             });
             if (mDone) {
-                mCommittedTransactions.clear();
-                mRemovedLayers.clear();
+                mUpdates.clear();
+                mDestroyedLayers.clear();
                 break;
             }
 
-            removedLayers = std::move(mRemovedLayers);
-            mRemovedLayers.clear();
-            committedTransactions = std::move(mCommittedTransactions);
-            mCommittedTransactions.clear();
+            destroyedLayers = std::move(mDestroyedLayers);
+            mDestroyedLayers.clear();
+            committedUpdates = std::move(mUpdates);
+            mUpdates.clear();
         } // unlock mMainThreadLock
 
-        if (!committedTransactions.empty() || !removedLayers.empty()) {
-            addEntry(committedTransactions, removedLayers);
+        if (!committedUpdates.empty() || !destroyedLayers.empty()) {
+            addEntry(committedUpdates, destroyedLayers);
         }
     }
 }
 
-void TransactionTracing::addEntry(const std::vector<CommittedTransactions>& committedTransactions,
-                                  const std::vector<int32_t>& removedLayers) {
+void TransactionTracing::addEntry(const std::vector<CommittedUpdates>& committedUpdates,
+                                  const std::vector<uint32_t>& destroyedLayers) {
     ATRACE_CALL();
     std::scoped_lock lock(mTraceLock);
     std::vector<std::string> removedEntries;
@@ -222,59 +154,27 @@
 
     while (auto incomingTransaction = mTransactionQueue.pop()) {
         auto transaction = *incomingTransaction;
-        int32_t layerCount = transaction.layer_changes_size();
-        for (int i = 0; i < layerCount; i++) {
-            auto layer = transaction.mutable_layer_changes(i);
-            layer->set_layer_id(
-                mProtoParser.mMapper->getLayerId(reinterpret_cast<BBinder*>(layer->layer_id())));
-            if ((layer->what() & layer_state_t::eReparent) && layer->parent_id() != -1) {
-                layer->set_parent_id(
-                    mProtoParser.mMapper->getLayerId(reinterpret_cast<BBinder*>(
-                        layer->parent_id())));
-            }
-
-            if ((layer->what() & layer_state_t::eRelativeLayerChanged) &&
-                layer->relative_parent_id() != -1) {
-                layer->set_relative_parent_id(
-                    mProtoParser.mMapper->getLayerId(reinterpret_cast<BBinder*>(
-                        layer->relative_parent_id())));
-            }
-
-            if (layer->has_window_info_handle() &&
-                layer->window_info_handle().crop_layer_id() != -1) {
-                auto input = layer->mutable_window_info_handle();
-                input->set_crop_layer_id(
-                        mProtoParser.mMapper->getLayerId(reinterpret_cast<BBinder*>(
-                            input->crop_layer_id())));
-            }
-        }
         mQueuedTransactions[incomingTransaction->transaction_id()] = transaction;
         delete incomingTransaction;
     }
-    for (const CommittedTransactions& entry : committedTransactions) {
-        entryProto.set_elapsed_realtime_nanos(entry.timestamp);
-        entryProto.set_vsync_id(entry.vsyncId);
+    for (const CommittedUpdates& update : committedUpdates) {
+        entryProto.set_elapsed_realtime_nanos(update.timestamp);
+        entryProto.set_vsync_id(update.vsyncId);
         entryProto.mutable_added_layers()->Reserve(
-                static_cast<int32_t>(entry.createdLayerIds.size()));
+                static_cast<int32_t>(update.createdLayers.size()));
 
-        for (const int32_t& id : entry.createdLayerIds) {
-            auto it = mCreatedLayers.find(id);
-            if (it != mCreatedLayers.end()) {
-                entryProto.mutable_added_layers()->Add(std::move(it->second));
-                mCreatedLayers.erase(it);
-            } else {
-                ALOGW("Could not created layer with id %d", id);
-            }
+        for (const auto& args : update.createdLayers) {
+            entryProto.mutable_added_layers()->Add(std::move(mProtoParser.toProto(args)));
         }
 
-        entryProto.mutable_removed_layers()->Reserve(static_cast<int32_t>(removedLayers.size()));
-        for (auto& removedLayer : removedLayers) {
-            entryProto.mutable_removed_layers()->Add(removedLayer);
-            mCreatedLayers.erase(removedLayer);
+        entryProto.mutable_destroyed_layers()->Reserve(
+                static_cast<int32_t>(destroyedLayers.size()));
+        for (auto& destroyedLayer : destroyedLayers) {
+            entryProto.mutable_destroyed_layers()->Add(destroyedLayer);
         }
         entryProto.mutable_transactions()->Reserve(
-                static_cast<int32_t>(entry.transactionIds.size()));
-        for (const uint64_t& id : entry.transactionIds) {
+                static_cast<int32_t>(update.transactionIds.size()));
+        for (const uint64_t& id : update.transactionIds) {
             auto it = mQueuedTransactions.find(id);
             if (it != mQueuedTransactions.end()) {
                 entryProto.mutable_transactions()->Add(std::move(it->second));
@@ -284,13 +184,21 @@
             }
         }
 
-        entryProto.mutable_removed_layer_handles()->Reserve(
-                static_cast<int32_t>(mRemovedLayerHandles.size()));
-        for (auto& [handle, layerId] : mRemovedLayerHandles) {
-            entryProto.mutable_removed_layer_handles()->Add(layerId);
-            mLayerHandles.erase(handle);
+        entryProto.mutable_destroyed_layer_handles()->Reserve(
+                static_cast<int32_t>(update.destroyedLayerHandles.size()));
+        for (auto layerId : update.destroyedLayerHandles) {
+            entryProto.mutable_destroyed_layer_handles()->Add(layerId);
         }
-        mRemovedLayerHandles.clear();
+
+        entryProto.set_displays_changed(update.displayInfoChanged);
+        if (update.displayInfoChanged) {
+            entryProto.mutable_displays()->Reserve(
+                    static_cast<int32_t>(update.displayInfos.size()));
+            for (auto& [layerStack, displayInfo] : update.displayInfos) {
+                entryProto.mutable_displays()->Add(
+                        std::move(mProtoParser.toProto(displayInfo, layerStack.id)));
+            }
+        }
 
         std::string serializedProto;
         entryProto.SerializeToString(&serializedProto);
@@ -311,7 +219,7 @@
 }
 
 void TransactionTracing::flush(int64_t vsyncId) {
-    while (!mPendingTransactions.empty() || !mPendingRemovedLayers.empty()) {
+    while (!mPendingUpdates.empty() || !mPendingDestroyedLayers.empty()) {
         tryPushToTracingThread();
     }
     std::unique_lock<std::mutex> lock(mTraceLock);
@@ -325,54 +233,21 @@
     });
 }
 
-void TransactionTracing::onLayerAdded(BBinder* layerHandle, int layerId, const std::string& name,
-                                      uint32_t flags, int parentId) {
-    std::scoped_lock lock(mTraceLock);
-    TracingLayerCreationArgs args{layerId, name, flags, parentId, -1 /* mirrorFromId */};
-    if (mLayerHandles.find(layerHandle) != mLayerHandles.end()) {
-        ALOGW("Duplicate handles found. %p", layerHandle);
-    }
-    mLayerHandles[layerHandle] = layerId;
-    mCreatedLayers[layerId] = mProtoParser.toProto(args);
-}
-
-void TransactionTracing::onMirrorLayerAdded(BBinder* layerHandle, int layerId,
-                                            const std::string& name, int mirrorFromId) {
-    std::scoped_lock lock(mTraceLock);
-    TracingLayerCreationArgs args{layerId, name, 0 /* flags */, -1 /* parentId */, mirrorFromId};
-    if (mLayerHandles.find(layerHandle) != mLayerHandles.end()) {
-        ALOGW("Duplicate handles found. %p", layerHandle);
-    }
-    mLayerHandles[layerHandle] = layerId;
-    mCreatedLayers[layerId] = mProtoParser.toProto(args);
-}
-
 void TransactionTracing::onLayerRemoved(int32_t layerId) {
-    mPendingRemovedLayers.emplace_back(layerId);
+    mPendingDestroyedLayers.emplace_back(layerId);
     tryPushToTracingThread();
 }
 
-void TransactionTracing::onHandleRemoved(BBinder* layerHandle) {
-    std::scoped_lock lock(mTraceLock);
-    auto it = mLayerHandles.find(layerHandle);
-    if (it == mLayerHandles.end()) {
-        ALOGW("handle not found. %p", layerHandle);
-        return;
-    }
-    mRemovedLayerHandles.emplace_back(layerHandle, it->second);
-}
-
 void TransactionTracing::tryPushToTracingThread() {
     // Try to acquire the lock from main thread.
     if (mMainThreadLock.try_lock()) {
         // We got the lock! Collect any pending transactions and continue.
-        mCommittedTransactions.insert(mCommittedTransactions.end(),
-                                      std::make_move_iterator(mPendingTransactions.begin()),
-                                      std::make_move_iterator(mPendingTransactions.end()));
-        mPendingTransactions.clear();
-        mRemovedLayers.insert(mRemovedLayers.end(), mPendingRemovedLayers.begin(),
-                              mPendingRemovedLayers.end());
-        mPendingRemovedLayers.clear();
+        mUpdates.insert(mUpdates.end(), std::make_move_iterator(mPendingUpdates.begin()),
+                        std::make_move_iterator(mPendingUpdates.end()));
+        mPendingUpdates.clear();
+        mDestroyedLayers.insert(mDestroyedLayers.end(), mPendingDestroyedLayers.begin(),
+                                mPendingDestroyedLayers.end());
+        mPendingDestroyedLayers.clear();
         mTransactionsAvailableCv.notify_one();
         mMainThreadLock.unlock();
     } else {
@@ -394,24 +269,28 @@
     // Merge layer states to starting transaction state.
     for (const proto::TransactionState& transaction : removedEntry.transactions()) {
         for (const proto::LayerState& layerState : transaction.layer_changes()) {
-            auto it = mStartingStates.find((int32_t)layerState.layer_id());
+            auto it = mStartingStates.find(layerState.layer_id());
             if (it == mStartingStates.end()) {
-                ALOGW("Could not find layer id %d", (int32_t)layerState.layer_id());
+                ALOGW("Could not find layer id %d", layerState.layer_id());
                 continue;
             }
             mProtoParser.mergeFromProto(layerState, it->second);
         }
     }
 
-    for (const int32_t removedLayerHandleId : removedEntry.removed_layer_handles()) {
-        mRemovedLayerHandlesAtStart.insert(removedLayerHandleId);
+    for (const uint32_t destroyedLayerHandleId : removedEntry.destroyed_layer_handles()) {
+        mRemovedLayerHandlesAtStart.insert(destroyedLayerHandleId);
     }
 
     // Clean up stale starting states since the layer has been removed and the buffer does not
     // contain any references to the layer.
-    for (const int32_t removedLayerId : removedEntry.removed_layers()) {
-        mStartingStates.erase(removedLayerId);
-        mRemovedLayerHandlesAtStart.erase(removedLayerId);
+    for (const uint32_t destroyedLayerId : removedEntry.destroyed_layers()) {
+        mStartingStates.erase(destroyedLayerId);
+        mRemovedLayerHandlesAtStart.erase(destroyedLayerId);
+    }
+
+    if (removedEntry.displays_changed()) {
+        mProtoParser.fromProto(removedEntry.displays(), mStartingDisplayInfos);
     }
 }
 
@@ -434,10 +313,15 @@
     transactionProto.set_post_time(mStartingTimestamp);
     entryProto->mutable_transactions()->Add(std::move(transactionProto));
 
-    entryProto->mutable_removed_layer_handles()->Reserve(
+    entryProto->mutable_destroyed_layer_handles()->Reserve(
             static_cast<int32_t>(mRemovedLayerHandlesAtStart.size()));
-    for (const int32_t removedLayerHandleId : mRemovedLayerHandlesAtStart) {
-        entryProto->mutable_removed_layer_handles()->Add(removedLayerHandleId);
+    for (const uint32_t destroyedLayerHandleId : mRemovedLayerHandlesAtStart) {
+        entryProto->mutable_destroyed_layer_handles()->Add(destroyedLayerHandleId);
+    }
+
+    entryProto->mutable_displays()->Reserve(static_cast<int32_t>(mStartingDisplayInfos.size()));
+    for (auto& [layerStack, displayInfo] : mStartingDisplayInfos) {
+        entryProto->mutable_displays()->Add(mProtoParser.toProto(displayInfo, layerStack.id));
     }
 }
 
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.h b/services/surfaceflinger/Tracing/TransactionTracing.h
index ae01d3c..f27e7a9 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.h
+++ b/services/surfaceflinger/Tracing/TransactionTracing.h
@@ -25,8 +25,12 @@
 #include <mutex>
 #include <thread>
 
-#include "RingBuffer.h"
+#include "Display/DisplayMap.h"
+#include "FrontEnd/DisplayInfo.h"
+#include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/Update.h"
 #include "LocklessStack.h"
+#include "RingBuffer.h"
 #include "TransactionProtoParser.h"
 
 using namespace android::surfaceflinger;
@@ -55,22 +59,22 @@
     ~TransactionTracing();
 
     void addQueuedTransaction(const TransactionState&);
-    void addCommittedTransactions(std::vector<TransactionState>& transactions, int64_t vsyncId);
+    void addCommittedTransactions(
+            int64_t vsyncId, nsecs_t commitTime, frontend::Update& update,
+            const display::DisplayMap<ui::LayerStack, frontend::DisplayInfo>& displayInfos,
+            bool displayInfoChanged);
     status_t writeToFile(std::string filename = FILE_NAME);
     void setBufferSize(size_t bufferSizeInBytes);
-    void onLayerAdded(BBinder* layerHandle, int layerId, const std::string& name, uint32_t flags,
-                      int parentId);
-    void onMirrorLayerAdded(BBinder* layerHandle, int layerId, const std::string& name,
-                            int mirrorFromId);
     void onLayerRemoved(int layerId);
-    void onHandleRemoved(BBinder* layerHandle);
-    void onLayerAddedToDrawingState(int layerId, int64_t vsyncId);
     void dump(std::string&) const;
     static constexpr auto CONTINUOUS_TRACING_BUFFER_SIZE = 512 * 1024;
     static constexpr auto ACTIVE_TRACING_BUFFER_SIZE = 100 * 1024 * 1024;
+    // version 1 - switching to support new frontend
+    static constexpr auto TRACING_VERSION = 1;
 
 private:
     friend class TransactionTracingTest;
+    friend class SurfaceFlinger;
 
     static constexpr auto FILE_NAME = "/data/misc/wmtrace/transactions_trace.winscope";
 
@@ -83,16 +87,12 @@
     LocklessStack<proto::TransactionState> mTransactionQueue;
     nsecs_t mStartingTimestamp GUARDED_BY(mTraceLock);
     std::unordered_map<int, proto::LayerCreationArgs> mCreatedLayers GUARDED_BY(mTraceLock);
-    std::unordered_map<BBinder* /* layerHandle */, int32_t /* layerId */> mLayerHandles
+    std::map<uint32_t /* layerId */, TracingLayerState> mStartingStates GUARDED_BY(mTraceLock);
+    display::DisplayMap<ui::LayerStack, frontend::DisplayInfo> mStartingDisplayInfos
             GUARDED_BY(mTraceLock);
-    std::vector<std::pair<BBinder* /* layerHandle */, int32_t /* layerId */>> mRemovedLayerHandles
-            GUARDED_BY(mTraceLock);
-    std::map<int32_t /* layerId */, TracingLayerState> mStartingStates GUARDED_BY(mTraceLock);
-    std::set<int32_t /* layerId */> mRemovedLayerHandlesAtStart GUARDED_BY(mTraceLock);
-    TransactionProtoParser mProtoParser GUARDED_BY(mTraceLock);
-    // Parses the transaction to proto without holding any tracing locks so we can generate proto
-    // in the binder thread without any contention.
-    TransactionProtoParser mLockfreeProtoParser;
+
+    std::set<uint32_t /* layerId */> mRemovedLayerHandlesAtStart GUARDED_BY(mTraceLock);
+    TransactionProtoParser mProtoParser;
 
     // We do not want main thread to block so main thread will try to acquire mMainThreadLock,
     // otherwise will push data to temporary container.
@@ -101,27 +101,29 @@
     bool mDone GUARDED_BY(mMainThreadLock) = false;
     std::condition_variable mTransactionsAvailableCv;
     std::condition_variable mTransactionsAddedToBufferCv;
-    struct CommittedTransactions {
+    struct CommittedUpdates {
         std::vector<uint64_t> transactionIds;
-        std::vector<int32_t> createdLayerIds;
+        std::vector<LayerCreationArgs> createdLayers;
+        std::vector<uint32_t> destroyedLayerHandles;
+        bool displayInfoChanged;
+        display::DisplayMap<ui::LayerStack, frontend::DisplayInfo> displayInfos;
         int64_t vsyncId;
         int64_t timestamp;
     };
-    std::vector<CommittedTransactions> mCommittedTransactions GUARDED_BY(mMainThreadLock);
-    std::vector<CommittedTransactions> mPendingTransactions; // only accessed by main thread
+    std::vector<CommittedUpdates> mUpdates GUARDED_BY(mMainThreadLock);
+    std::vector<CommittedUpdates> mPendingUpdates; // only accessed by main thread
 
-    std::vector<int32_t /* layerId */> mRemovedLayers GUARDED_BY(mMainThreadLock);
-    std::vector<int32_t /* layerId */> mPendingRemovedLayers; // only accessed by main thread
+    std::vector<uint32_t /* layerId */> mDestroyedLayers GUARDED_BY(mMainThreadLock);
+    std::vector<uint32_t /* layerId */> mPendingDestroyedLayers; // only accessed by main thread
 
     proto::TransactionTraceFile createTraceFileProto() const;
     void loop();
-    void addEntry(const std::vector<CommittedTransactions>& committedTransactions,
-                  const std::vector<int32_t>& removedLayers) EXCLUDES(mTraceLock);
+    void addEntry(const std::vector<CommittedUpdates>& committedTransactions,
+                  const std::vector<uint32_t>& removedLayers) EXCLUDES(mTraceLock);
     int32_t getLayerIdLocked(const sp<IBinder>& layerHandle) REQUIRES(mTraceLock);
     void tryPushToTracingThread() EXCLUDES(mMainThreadLock);
     void addStartingStateToProtoLocked(proto::TransactionTraceFile& proto) REQUIRES(mTraceLock);
     void updateStartingStateLocked(const proto::TransactionTraceEntry& entry) REQUIRES(mTraceLock);
-    CommittedTransactions& findOrCreateCommittedTransactionRecord(int64_t vsyncId);
     // TEST
     // Wait until all the committed transactions for the specified vsync id are added to the buffer.
     void flush(int64_t vsyncId) EXCLUDES(mMainThreadLock);
diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
index 31f4723..68a6cfc 100644
--- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
@@ -14,175 +14,32 @@
  * limitations under the License.
  */
 
+#include <ios>
+#include <memory>
+#include <vector>
+#include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/RequestedLayerState.h"
+#include "Tracing/LayerTracing.h"
+#include "TransactionState.h"
+#include "cutils/properties.h"
 #undef LOG_TAG
 #define LOG_TAG "LayerTraceGenerator"
 //#define LOG_NDEBUG 0
 
-#include <TestableSurfaceFlinger.h>
 #include <Tracing/TransactionProtoParser.h>
-#include <binder/IPCThreadState.h>
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
 #include <gui/LayerState.h>
 #include <log/log.h>
-#include <mock/MockEventThread.h>
 #include <renderengine/ExternalTexture.h>
-#include <renderengine/mock/RenderEngine.h>
 #include <utils/String16.h>
+#include <filesystem>
+#include <fstream>
 #include <string>
+#include "LayerProtoHelper.h"
 
 #include "LayerTraceGenerator.h"
 
 namespace android {
-
-class Factory final : public surfaceflinger::Factory {
-public:
-    ~Factory() = default;
-
-    std::unique_ptr<HWComposer> createHWComposer(const std::string&) override { return nullptr; }
-
-    std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
-            Fps /*currentRefreshRate*/) override {
-        return std::make_unique<scheduler::FakePhaseOffsets>();
-    }
-
-    sp<StartPropertySetThread> createStartPropertySetThread(
-            bool /* timestampPropertyValue */) override {
-        return sp<StartPropertySetThread>();
-    }
-
-    sp<DisplayDevice> createDisplayDevice(DisplayDeviceCreationArgs& /* creationArgs */) override {
-        return sp<DisplayDevice>();
-    }
-
-    sp<GraphicBuffer> createGraphicBuffer(uint32_t /* width */, uint32_t /* height */,
-                                          PixelFormat /* format */, uint32_t /* layerCount */,
-                                          uint64_t /* usage */,
-                                          std::string /* requestorName */) override {
-        return sp<GraphicBuffer>();
-    }
-
-    void createBufferQueue(sp<IGraphicBufferProducer>* /* outProducer */,
-                           sp<IGraphicBufferConsumer>* /* outConsumer */,
-                           bool /* consumerIsSurfaceFlinger */) override {}
-
-    std::unique_ptr<surfaceflinger::NativeWindowSurface> createNativeWindowSurface(
-            const sp<IGraphicBufferProducer>& /* producer */) override {
-        return nullptr;
-    }
-
-    std::unique_ptr<compositionengine::CompositionEngine> createCompositionEngine() override {
-        return compositionengine::impl::createCompositionEngine();
-    }
-
-    sp<Layer> createBufferStateLayer(const LayerCreationArgs& args) {
-        return sp<Layer>::make(args);
-    }
-
-    sp<Layer> createEffectLayer(const LayerCreationArgs& args) { return sp<Layer>::make(args); }
-
-    sp<LayerFE> createLayerFE(const std::string& layerName) { return sp<LayerFE>::make(layerName); }
-
-    std::unique_ptr<FrameTracer> createFrameTracer() override {
-        return std::make_unique<testing::NiceMock<mock::FrameTracer>>();
-    }
-
-    std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
-            std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid = 0) override {
-        return std::make_unique<testing::NiceMock<mock::FrameTimeline>>(timeStats,
-                                                                        surfaceFlingerPid);
-    }
-};
-
-class FakeExternalTexture : public renderengine::ExternalTexture {
-    const sp<GraphicBuffer> mNullBuffer = nullptr;
-    uint32_t mWidth;
-    uint32_t mHeight;
-    uint64_t mId;
-    PixelFormat mPixelFormat;
-    uint64_t mUsage;
-
-public:
-    FakeExternalTexture(uint32_t width, uint32_t height, uint64_t id, PixelFormat pixelFormat,
-                        uint64_t usage)
-          : mWidth(width), mHeight(height), mId(id), mPixelFormat(pixelFormat), mUsage(usage) {}
-    const sp<GraphicBuffer>& getBuffer() const { return mNullBuffer; }
-    bool hasSameBuffer(const renderengine::ExternalTexture& other) const override {
-        return getId() == other.getId();
-    }
-    uint32_t getWidth() const override { return mWidth; }
-    uint32_t getHeight() const override { return mHeight; }
-    uint64_t getId() const override { return mId; }
-    PixelFormat getPixelFormat() const override { return mPixelFormat; }
-    uint64_t getUsage() const override { return mUsage; }
-    ~FakeExternalTexture() = default;
-};
-
-class MockSurfaceFlinger : public SurfaceFlinger {
-public:
-    MockSurfaceFlinger(Factory& factory)
-          : SurfaceFlinger(factory, SurfaceFlinger::SkipInitialization) {}
-    std::shared_ptr<renderengine::ExternalTexture> getExternalTextureFromBufferData(
-            BufferData& bufferData, const char* /* layerName */,
-            uint64_t /* transactionId */) override {
-        return std::make_shared<FakeExternalTexture>(bufferData.getWidth(), bufferData.getHeight(),
-                                                     bufferData.getId(),
-                                                     bufferData.getPixelFormat(),
-                                                     bufferData.getUsage());
-    };
-
-    // b/220017192 migrate from transact codes to ISurfaceComposer apis
-    void setLayerTracingFlags(int32_t flags) {
-        Parcel data;
-        Parcel reply;
-        data.writeInterfaceToken(String16("android.ui.ISurfaceComposer"));
-        data.writeInt32(flags);
-        transact(1033, data, &reply, 0 /* flags */);
-    }
-
-    void setLayerTraceSize(int32_t sizeInKb) {
-        Parcel data;
-        Parcel reply;
-        data.writeInterfaceToken(String16("android.ui.ISurfaceComposer"));
-        data.writeInt32(sizeInKb);
-        transact(1029, data, &reply, 0 /* flags */);
-    }
-
-    void startLayerTracing(int64_t traceStartTime) {
-        Parcel data;
-        Parcel reply;
-        data.writeInterfaceToken(String16("android.ui.ISurfaceComposer"));
-        data.writeInt32(1);
-        data.writeInt64(traceStartTime);
-        transact(1025, data, &reply, 0 /* flags */);
-    }
-
-    void stopLayerTracing(const char* tracePath) {
-        Parcel data;
-        Parcel reply;
-        data.writeInterfaceToken(String16("android.ui.ISurfaceComposer"));
-        data.writeInt32(2);
-        data.writeCString(tracePath);
-        transact(1025, data, &reply, 0 /* flags */);
-    }
-};
-
-class TraceGenFlingerDataMapper : public TransactionProtoParser::FlingerDataMapper {
-public:
-    std::unordered_map<int32_t /*layerId*/, sp<IBinder> /* handle */> mLayerHandles;
-    sp<IBinder> getLayerHandle(int32_t layerId) const override {
-        if (layerId == -1) {
-            ALOGE("Error: Called with layer=%d", layerId);
-            return nullptr;
-        }
-        auto it = mLayerHandles.find(layerId);
-        if (it == mLayerHandles.end()) {
-            ALOGE("Error: Could not find handle for layer=%d", layerId);
-            return nullptr;
-        }
-        return it->second;
-    }
-};
+using namespace ftl::flag_operators;
 
 bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile,
                                    const char* outputLayersTracePath) {
@@ -191,82 +48,127 @@
         return false;
     }
 
-    Factory factory;
-    sp<MockSurfaceFlinger> flingerPtr = sp<MockSurfaceFlinger>::make(factory);
-    TestableSurfaceFlinger flinger(flingerPtr);
-    flinger.setupRenderEngine(
-            std::make_unique<testing::NiceMock<renderengine::mock::RenderEngine>>());
-    flinger.setupMockScheduler({.useNiceMock = true});
+    TransactionProtoParser parser(std::make_unique<TransactionProtoParser::FlingerDataMapper>());
 
-    Hwc2::mock::Composer* composerPtr = new testing::NiceMock<Hwc2::mock::Composer>();
-    flinger.setupComposer(std::unique_ptr<Hwc2::Composer>(composerPtr));
-    flinger.mutableMaxRenderTargetSize() = 16384;
+    // frontend
+    frontend::LayerLifecycleManager lifecycleManager;
+    frontend::LayerHierarchyBuilder hierarchyBuilder{{}};
+    frontend::LayerSnapshotBuilder snapshotBuilder;
+    display::DisplayMap<ui::LayerStack, frontend::DisplayInfo> displayInfos;
 
-    flingerPtr->setLayerTracingFlags(LayerTracing::TRACE_INPUT | LayerTracing::TRACE_BUFFERS);
-    flingerPtr->setLayerTraceSize(512 * 1024); // 512MB buffer size
-    flingerPtr->startLayerTracing(traceFile.entry(0).elapsed_realtime_nanos());
-    std::unique_ptr<TraceGenFlingerDataMapper> mapper =
-            std::make_unique<TraceGenFlingerDataMapper>();
-    TraceGenFlingerDataMapper* dataMapper = mapper.get();
-    TransactionProtoParser parser(std::move(mapper));
+    renderengine::ShadowSettings globalShadowSettings{.ambientColor = {1, 1, 1, 1}};
+    char value[PROPERTY_VALUE_MAX];
+    property_get("ro.surface_flinger.supports_background_blur", value, "0");
+    bool supportsBlur = atoi(value);
+
+    LayerTracing layerTracing;
+    layerTracing.setTraceFlags(LayerTracing::TRACE_INPUT | LayerTracing::TRACE_BUFFERS);
+    // 10MB buffer size (large enough to hold a single entry)
+    layerTracing.setBufferSize(10 * 1024 * 1024);
+    layerTracing.enable();
+    layerTracing.writeToFile(outputLayersTracePath);
+    std::ofstream out(outputLayersTracePath, std::ios::binary | std::ios::app);
 
     ALOGD("Generating %d transactions...", traceFile.entry_size());
     for (int i = 0; i < traceFile.entry_size(); i++) {
+        // parse proto
         proto::TransactionTraceEntry entry = traceFile.entry(i);
         ALOGV("    Entry %04d/%04d for time=%" PRId64 " vsyncid=%" PRId64
               " layers +%d -%d handles -%d transactions=%d",
               i, traceFile.entry_size(), entry.elapsed_realtime_nanos(), entry.vsync_id(),
-              entry.added_layers_size(), entry.removed_layers_size(),
-              entry.removed_layer_handles_size(), entry.transactions_size());
+              entry.added_layers_size(), entry.destroyed_layers_size(),
+              entry.destroyed_layer_handles_size(), entry.transactions_size());
 
+        std::vector<std::unique_ptr<frontend::RequestedLayerState>> addedLayers;
+        addedLayers.reserve((size_t)entry.added_layers_size());
         for (int j = 0; j < entry.added_layers_size(); j++) {
-            // create layers
-            TracingLayerCreationArgs tracingArgs;
-            parser.fromProto(entry.added_layers(j), tracingArgs);
-
-            gui::CreateSurfaceResult outResult;
-            LayerCreationArgs args(flinger.flinger(), nullptr /* client */, tracingArgs.name,
-                                   tracingArgs.flags, LayerMetadata(),
-                                   std::make_optional<int32_t>(tracingArgs.layerId));
-
-            if (tracingArgs.mirrorFromId == -1) {
-                sp<IBinder> parentHandle = nullptr;
-                if ((tracingArgs.parentId != -1) &&
-                    (dataMapper->mLayerHandles.find(tracingArgs.parentId) ==
-                     dataMapper->mLayerHandles.end())) {
-                    args.addToRoot = false;
-                } else if (tracingArgs.parentId != -1) {
-                    parentHandle = dataMapper->getLayerHandle(tracingArgs.parentId);
-                }
-                flinger.createLayer(args, parentHandle, outResult);
-            } else {
-                sp<IBinder> mirrorFromHandle = dataMapper->getLayerHandle(tracingArgs.mirrorFromId);
-                flinger.mirrorLayer(args, mirrorFromHandle, outResult);
-            }
-            LOG_ALWAYS_FATAL_IF(outResult.layerId != tracingArgs.layerId,
-                                "Could not create layer expected:%d actual:%d", tracingArgs.layerId,
-                                outResult.layerId);
-            dataMapper->mLayerHandles[tracingArgs.layerId] = outResult.handle;
+            LayerCreationArgs args;
+            parser.fromProto(entry.added_layers(j), args);
+            addedLayers.emplace_back(std::make_unique<frontend::RequestedLayerState>(args));
         }
 
+        std::vector<TransactionState> transactions;
+        transactions.reserve((size_t)entry.transactions_size());
         for (int j = 0; j < entry.transactions_size(); j++) {
             // apply transactions
             TransactionState transaction = parser.fromProto(entry.transactions(j));
-            flinger.setTransactionStateInternal(transaction);
+            transactions.emplace_back(std::move(transaction));
         }
 
-        const auto frameTime = TimePoint::fromNs(entry.elapsed_realtime_nanos());
-        const auto vsyncId = VsyncId{entry.vsync_id()};
-        flinger.commit(frameTime, vsyncId);
-
-        for (int j = 0; j < entry.removed_layer_handles_size(); j++) {
-            dataMapper->mLayerHandles.erase(entry.removed_layer_handles(j));
+        std::vector<uint32_t> destroyedHandles;
+        destroyedHandles.reserve((size_t)entry.destroyed_layer_handles_size());
+        for (int j = 0; j < entry.destroyed_layer_handles_size(); j++) {
+            destroyedHandles.push_back(entry.destroyed_layer_handles(j));
         }
+
+        bool displayChanged = entry.displays_changed();
+        if (displayChanged) {
+            parser.fromProto(entry.displays(), displayInfos);
+        }
+
+        // apply updates
+        lifecycleManager.addLayers(std::move(addedLayers));
+        lifecycleManager.applyTransactions(transactions);
+        lifecycleManager.onHandlesDestroyed(destroyedHandles, /*ignoreUnknownHandles=*/true);
+
+        if (lifecycleManager.getGlobalChanges().test(
+                    frontend::RequestedLayerState::Changes::Hierarchy)) {
+            hierarchyBuilder.update(lifecycleManager.getLayers(),
+                                    lifecycleManager.getDestroyedLayers());
+        }
+
+        frontend::LayerSnapshotBuilder::Args args{.root = hierarchyBuilder.getHierarchy(),
+                                                  .layerLifecycleManager = lifecycleManager,
+                                                  .displays = displayInfos,
+                                                  .displayChanges = displayChanged,
+                                                  .globalShadowSettings = globalShadowSettings,
+                                                  .supportsBlur = supportsBlur,
+                                                  .forceFullDamage = false,
+                                                  .supportedLayerGenericMetadata = {},
+                                                  .genericLayerMetadataKeyMap = {}};
+        snapshotBuilder.update(args);
+
+        bool visibleRegionsDirty = lifecycleManager.getGlobalChanges().any(
+                frontend::RequestedLayerState::Changes::VisibleRegion |
+                frontend::RequestedLayerState::Changes::Hierarchy |
+                frontend::RequestedLayerState::Changes::Visibility);
+
+        ALOGV("    layers:%04zu snapshots:%04zu changes:%s", lifecycleManager.getLayers().size(),
+              snapshotBuilder.getSnapshots().size(),
+              lifecycleManager.getGlobalChanges().string().c_str());
+
+        lifecycleManager.commitChanges();
+        // write layers trace
+        auto tracingFlags = LayerTracing::TRACE_INPUT | LayerTracing::TRACE_BUFFERS;
+        std::unordered_set<uint64_t> stackIdsToSkip;
+        if ((tracingFlags & LayerTracing::TRACE_VIRTUAL_DISPLAYS) == 0) {
+            for (const auto& displayInfo : displayInfos) {
+                if (displayInfo.second.isVirtual) {
+                    stackIdsToSkip.insert(displayInfo.first.id);
+                }
+            }
+        }
+
+        const frontend::LayerHierarchy& root = hierarchyBuilder.getHierarchy();
+
+        LayersProto layersProto;
+        for (auto& [child, variant] : root.mChildren) {
+            if (variant != frontend::LayerHierarchy::Variant::Attached ||
+                stackIdsToSkip.find(child->getLayer()->layerStack.id) != stackIdsToSkip.end()) {
+                continue;
+            }
+            LayerProtoHelper::writeHierarchyToProto(layersProto, *child, snapshotBuilder, {},
+                                                    tracingFlags);
+        }
+
+        auto displayProtos = LayerProtoHelper::writeDisplayInfoToProto(displayInfos);
+        layerTracing.notify(visibleRegionsDirty, entry.elapsed_realtime_nanos(), entry.vsync_id(),
+                            &layersProto, {}, &displayProtos);
+        layerTracing.appendToStream(out);
     }
-
-    flingerPtr->stopLayerTracing(outputLayersTracePath);
+    layerTracing.disable("", /*writeToFile=*/false);
+    out.close();
     ALOGD("End of generating trace file. File written to %s", outputLayersTracePath);
-    dataMapper->mLayerHandles.clear();
     return true;
 }
 
diff --git a/services/surfaceflinger/Tracing/tools/main.cpp b/services/surfaceflinger/Tracing/tools/main.cpp
index 9f9ae48..c440c19 100644
--- a/services/surfaceflinger/Tracing/tools/main.cpp
+++ b/services/surfaceflinger/Tracing/tools/main.cpp
@@ -53,9 +53,6 @@
     ALOGD("Generating %s...", outputLayersTracePath);
     std::cout << "Generating " << outputLayersTracePath << "\n";
 
-    // sink any log spam from the stubbed surfaceflinger
-    __android_log_set_logger([](const struct __android_log_message* /* log_message */) {});
-
     if (!LayerTraceGenerator().generate(transactionTraceFile, outputLayersTracePath)) {
         std::cout << "Error: Failed to generate layers trace " << outputLayersTracePath;
         return -1;
diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h
index 6c5a8b2..2daea25 100644
--- a/services/surfaceflinger/TransactionState.h
+++ b/services/surfaceflinger/TransactionState.h
@@ -20,6 +20,7 @@
 #include <memory>
 #include <mutex>
 #include <vector>
+#include "FrontEnd/LayerCreationArgs.h"
 #include "renderengine/ExternalTexture.h"
 
 #include <gui/LayerState.h>
@@ -39,6 +40,10 @@
     ResolvedComposerState() = default;
     ResolvedComposerState(ComposerState&& source) { state = std::move(source.state); }
     std::shared_ptr<renderengine::ExternalTexture> externalTexture;
+    uint32_t layerId = UNASSIGNED_LAYER_ID;
+    uint32_t parentId = UNASSIGNED_LAYER_ID;
+    uint32_t relativeParentId = UNASSIGNED_LAYER_ID;
+    uint32_t touchCropId = UNASSIGNED_LAYER_ID;
 };
 
 struct TransactionState {
@@ -85,7 +90,7 @@
         for (auto state = states.begin(); state != states.end();) {
             if (state->state.hasBufferChanges() && state->state.hasValidBuffer() &&
                 state->state.surface) {
-                int result = visitor(state->state);
+                int result = visitor(state->state, state->externalTexture);
                 if (result == STOP_TRAVERSAL) return;
                 if (result == DELETE_AND_CONTINUE_TRAVERSAL) {
                     state = states.erase(state);
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index cfb2032..6074bb7 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -247,7 +247,7 @@
 
     auto &mutableLayerHistory() { return mLayerHistory; }
 
-    auto refreshRateSelector() { return leaderSelectorPtr(); }
+    auto refreshRateSelector() { return pacesetterSelectorPtr(); }
 
     void replaceTouchTimer(int64_t millis) {
         if (mTouchTimer) {
@@ -402,9 +402,8 @@
     SurfaceFlinger *flinger() { return mFlinger.get(); }
     scheduler::TestableScheduler *scheduler() { return mScheduler; }
 
-    // Allow reading display state without locking, as if called on the SF main thread.
-    auto onInitializeDisplays() NO_THREAD_SAFETY_ANALYSIS {
-        return mFlinger->onInitializeDisplays();
+    void initializeDisplays() {
+        FTL_FAKE_GUARD(kMainThreadContext, mFlinger->initializeDisplays());
     }
 
     void setGlobalShadowSettings(FuzzedDataProvider *fdp) {
@@ -453,8 +452,7 @@
 
         LayersProto layersProto = mFlinger->dumpDrawingStateProto(fdp->ConsumeIntegral<uint32_t>());
         mFlinger->dumpOffscreenLayersProto(layersProto);
-        LayersTraceProto layersTraceProto{};
-        mFlinger->dumpDisplayProto(layersTraceProto);
+        mFlinger->dumpDisplayProto();
 
         result = fdp->ConsumeRandomLengthString().c_str();
         mFlinger->dumpHwc(result);
@@ -542,7 +540,7 @@
                 mFlinger->createDisplay(String8(fdp->ConsumeRandomLengthString().c_str()),
                                         fdp->ConsumeBool());
 
-        onInitializeDisplays();
+        initializeDisplays();
         mFlinger->getPhysicalDisplayToken(physicalDisplayId);
 
         mFlinger->mStartPropertySetThread =
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index f6b2c8e..f17d2e1 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -220,9 +220,9 @@
     sp<FuzzImplLayer> layer2 = sp<FuzzImplLayer>::make(flinger.flinger());
 
     for (int i = 0; i < historySize; ++i) {
-        historyV1.record(layer1.get(), time1, time1,
+        historyV1.record(layer1->getSequence(), layer1->getLayerProps(), time1, time1,
                          scheduler::LayerHistory::LayerUpdateType::Buffer);
-        historyV1.record(layer2.get(), time2, time2,
+        historyV1.record(layer2->getSequence(), layer2->getLayerProps(), time2, time2,
                          scheduler::LayerHistory::LayerUpdateType::Buffer);
         time1 += mFdp.PickValueInArray(kVsyncPeriods);
         time2 += mFdp.PickValueInArray(kVsyncPeriods);
diff --git a/services/surfaceflinger/layerproto/transactions.proto b/services/surfaceflinger/layerproto/transactions.proto
index 4c6a9cf..2c4eb10 100644
--- a/services/surfaceflinger/layerproto/transactions.proto
+++ b/services/surfaceflinger/layerproto/transactions.proto
@@ -40,6 +40,7 @@
     /* offset between real-time clock and elapsed time clock in nanoseconds.
        Calculated as: systemTime(SYSTEM_TIME_REALTIME) - systemTime(SYSTEM_TIME_MONOTONIC) */
     fixed64 real_to_elapsed_time_offset_nanos = 3;
+    uint32 version = 4;
 }
 
 message TransactionTraceEntry {
@@ -47,18 +48,47 @@
     int64 vsync_id = 2;
     repeated TransactionState transactions = 3;
     repeated LayerCreationArgs added_layers = 4;
-    repeated int32 removed_layers = 5;
+    repeated uint32 destroyed_layers = 5;
     repeated DisplayState added_displays = 6;
     repeated int32 removed_displays = 7;
-    repeated int32 removed_layer_handles = 8;
+    repeated uint32 destroyed_layer_handles = 8;
+    bool displays_changed = 9;
+    repeated DisplayInfo displays = 10;
+}
+
+message DisplayInfo {
+    uint32 layer_stack = 1;
+    int32 display_id = 2;
+    int32 logical_width = 3;
+    int32 logical_height = 4;
+    Transform transform_inverse = 5;
+    Transform transform = 6;
+    bool receives_input = 7;
+    bool is_secure = 8;
+    bool is_primary = 9;
+    bool is_virtual = 10;
+    int32 rotation_flags = 11;
+    int32 transform_hint = 12;
+
 }
 
 message LayerCreationArgs {
-    int32 layer_id = 1;
+    uint32 layer_id = 1;
     string name = 2;
     uint32 flags = 3;
-    int32 parent_id = 4;
-    int32 mirror_from_id = 5;
+    uint32 parent_id = 4;
+    uint32 mirror_from_id = 5;
+    bool add_to_root = 6;
+    uint32 layer_stack_to_mirror = 7;
+}
+
+message Transform {
+    float dsdx = 1;
+    float dtdx = 2;
+    float dtdy = 3;
+    float dsdy = 4;
+    float tx = 5;
+    float ty = 6;
 }
 
 message TransactionState {
@@ -74,7 +104,7 @@
 
 // Keep insync with layer_state_t
 message LayerState {
-    int64 layer_id = 1;
+    uint32 layer_id = 1;
     // Changes are split into ChangesLsb and ChangesMsb. First 32 bits are in ChangesLsb
     // and the next 32 bits are in ChangesMsb. This is needed because enums have to be
     // 32 bits and there's no nice way to put 64bit constants into .proto files.
@@ -164,8 +194,8 @@
     Matrix22 matrix = 11;
     float corner_radius = 12;
     uint32 background_blur_radius = 13;
-    int64 parent_id = 14;
-    int64 relative_parent_id = 15;
+    uint32 parent_id = 14;
+    uint32 relative_parent_id = 15;
 
     float alpha = 16;
     message Color3 {
@@ -220,14 +250,6 @@
     ColorTransformProto color_transform = 25;
     repeated BlurRegion blur_regions = 26;
 
-    message Transform {
-        float dsdx = 1;
-        float dtdx = 2;
-        float dtdy = 3;
-        float dsdy = 4;
-        float tx = 5;
-        float ty = 6;
-    }
     message WindowInfo {
         uint32 layout_params_flags = 1;
         int32 layout_params_type = 2;
@@ -236,7 +258,7 @@
         bool focusable = 5;
         bool has_wallpaper = 6;
         float global_scale_factor = 7;
-        int64 crop_layer_id = 8;
+        uint32 crop_layer_id = 8;
         bool replace_touchable_region_with_crop = 9;
         RectProto touchable_region_crop = 10;
         Transform transform = 11;
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index 6d12aa7..62b539a 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -125,6 +125,9 @@
     ],
     cpp_std: "experimental",
     gnu_extensions: false,
+    data: [
+        ":SurfaceFlinger_test",
+    ],
 }
 
 subdirs = [
diff --git a/services/surfaceflinger/tests/TextureFiltering_test.cpp b/services/surfaceflinger/tests/TextureFiltering_test.cpp
index e9b1fbb..d0ab105 100644
--- a/services/surfaceflinger/tests/TextureFiltering_test.cpp
+++ b/services/surfaceflinger/tests/TextureFiltering_test.cpp
@@ -187,8 +187,6 @@
 
 // Expect no filtering because the output source crop and output buffer are the same size.
 TEST_F(TextureFilteringTest, OutputSourceCropDisplayFrameMatchNoFiltering) {
-    // Transaction().setCrop(mLayer, Rect{25, 25, 75, 75}).apply();
-
     gui::DisplayCaptureArgs captureArgs;
     captureArgs.displayToken = mDisplay;
     captureArgs.width = 50;
@@ -224,4 +222,17 @@
     mCapture->expectColor(Rect{50, 25, 75, 75}, Color::BLUE);
 }
 
+// Expect no filtering because parent's position transform shouldn't scale the layer.
+TEST_F(TextureFilteringTest, ParentHasTransformNoFiltering) {
+    Transaction().setPosition(mParent, 100, 100).apply();
+
+    LayerCaptureArgs captureArgs;
+    captureArgs.layerHandle = mParent->getHandle();
+    captureArgs.sourceCrop = Rect{0, 0, 100, 100};
+    ScreenCapture::captureLayers(&mCapture, captureArgs);
+
+    mCapture->expectColor(Rect{0, 0, 50, 100}, Color::RED);
+    mCapture->expectColor(Rect{50, 0, 100, 100}, Color::BLUE);
+}
+
 } // namespace android
diff --git a/services/surfaceflinger/tests/WindowInfosListener_test.cpp b/services/surfaceflinger/tests/WindowInfosListener_test.cpp
index d71486f..042d37b 100644
--- a/services/surfaceflinger/tests/WindowInfosListener_test.cpp
+++ b/services/surfaceflinger/tests/WindowInfosListener_test.cpp
@@ -17,6 +17,7 @@
 #include <gtest/gtest.h>
 #include <gui/SurfaceComposerClient.h>
 #include <private/android_filesystem_config.h>
+#include <cstdint>
 #include <future>
 
 namespace android {
@@ -24,7 +25,8 @@
 using gui::DisplayInfo;
 using gui::WindowInfo;
 
-using WindowInfosPredicate = std::function<bool(const std::vector<WindowInfo>&)>;
+using WindowInfosPredicate =
+        std::function<bool(const std::vector<WindowInfo>&, const std::vector<DisplayInfo>&)>;
 
 class WindowInfosListenerTest : public ::testing::Test {
 protected:
@@ -41,8 +43,8 @@
               : mPredicate(std::move(predicate)), mPromise(promise) {}
 
         void onWindowInfosChanged(const std::vector<WindowInfo>& windowInfos,
-                                  const std::vector<DisplayInfo>&) override {
-            if (mPredicate(windowInfos)) {
+                                  const std::vector<DisplayInfo>& displayInfos) override {
+            if (mPredicate(windowInfos, displayInfos)) {
                 mPromise.set_value();
             }
         }
@@ -65,14 +67,24 @@
     }
 };
 
-std::optional<WindowInfo> findMatchingWindowInfo(WindowInfo targetWindowInfo,
-                                                 std::vector<WindowInfo> windowInfos) {
-    for (WindowInfo windowInfo : windowInfos) {
+const WindowInfo* findMatchingWindowInfo(const WindowInfo& targetWindowInfo,
+                                         const std::vector<WindowInfo>& windowInfos) {
+    for (const WindowInfo& windowInfo : windowInfos) {
         if (windowInfo.token == targetWindowInfo.token) {
-            return windowInfo;
+            return &windowInfo;
         }
     }
-    return std::nullopt;
+    return nullptr;
+}
+
+const DisplayInfo* findMatchingDisplayInfo(int32_t displayId,
+                                           const std::vector<DisplayInfo>& displayInfos) {
+    for (const DisplayInfo& displayInfo : displayInfos) {
+        if (displayInfo.displayId == displayId) {
+            return &displayInfo;
+        }
+    }
+    return nullptr;
 }
 
 TEST_F(WindowInfosListenerTest, WindowInfoAddedAndRemoved) {
@@ -92,15 +104,17 @@
             .setInputWindowInfo(surfaceControl, windowInfo)
             .apply();
 
-    auto windowPresent = [&](const std::vector<WindowInfo>& windowInfos) {
-        return findMatchingWindowInfo(windowInfo, windowInfos).has_value();
+    auto windowPresent = [&](const std::vector<WindowInfo>& windowInfos,
+                             const std::vector<DisplayInfo>&) {
+        return findMatchingWindowInfo(windowInfo, windowInfos);
     };
     ASSERT_TRUE(waitForWindowInfosPredicate(windowPresent));
 
     Transaction().reparent(surfaceControl, nullptr).apply();
 
-    auto windowNotPresent = [&](const std::vector<WindowInfo>& windowInfos) {
-        return !findMatchingWindowInfo(windowInfo, windowInfos).has_value();
+    auto windowNotPresent = [&](const std::vector<WindowInfo>& windowInfos,
+                                const std::vector<DisplayInfo>&) {
+        return !findMatchingWindowInfo(windowInfo, windowInfos);
     };
     ASSERT_TRUE(waitForWindowInfosPredicate(windowNotPresent));
 }
@@ -123,7 +137,8 @@
             .setInputWindowInfo(surfaceControl, windowInfo)
             .apply();
 
-    auto windowIsPresentAndTouchableRegionEmpty = [&](const std::vector<WindowInfo>& windowInfos) {
+    auto windowIsPresentAndTouchableRegionEmpty = [&](const std::vector<WindowInfo>& windowInfos,
+                                                      const std::vector<DisplayInfo>&) {
         auto foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos);
         if (!foundWindowInfo) {
             return false;
@@ -132,17 +147,26 @@
     };
     ASSERT_TRUE(waitForWindowInfosPredicate(windowIsPresentAndTouchableRegionEmpty));
 
-    Rect touchableRegions(0, 0, 50, 50);
-    windowInfo.addTouchableRegion(Rect(0, 0, 50, 50));
+    windowInfo.addTouchableRegion({0, 0, 50, 50});
     Transaction().setInputWindowInfo(surfaceControl, windowInfo).apply();
 
     auto windowIsPresentAndTouchableRegionMatches =
-            [&](const std::vector<WindowInfo>& windowInfos) {
+            [&](const std::vector<WindowInfo>& windowInfos,
+                const std::vector<DisplayInfo>& displayInfos) {
                 auto foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos);
                 if (!foundWindowInfo) {
                     return false;
                 }
-                return foundWindowInfo->touchableRegion.hasSameRects(windowInfo.touchableRegion);
+
+                auto displayInfo =
+                        findMatchingDisplayInfo(foundWindowInfo->displayId, displayInfos);
+                if (!displayInfo) {
+                    return false;
+                }
+
+                auto touchableRegion =
+                        displayInfo->transform.transform(foundWindowInfo->touchableRegion);
+                return touchableRegion.hasSameRects(windowInfo.touchableRegion);
             };
     ASSERT_TRUE(waitForWindowInfosPredicate(windowIsPresentAndTouchableRegionMatches));
 }
diff --git a/services/surfaceflinger/tests/tracing/testdata/layers_trace_boot.winscope b/services/surfaceflinger/tests/tracing/testdata/layers_trace_boot.winscope
index 9e4005c..296d2fd 100644
--- a/services/surfaceflinger/tests/tracing/testdata/layers_trace_boot.winscope
+++ b/services/surfaceflinger/tests/tracing/testdata/layers_trace_boot.winscope
Binary files differ
diff --git a/services/surfaceflinger/tests/tracing/testdata/layers_trace_nodisplayfound.winscope b/services/surfaceflinger/tests/tracing/testdata/layers_trace_nodisplayfound.winscope
index 16a91ee..ae54415 100644
--- a/services/surfaceflinger/tests/tracing/testdata/layers_trace_nodisplayfound.winscope
+++ b/services/surfaceflinger/tests/tracing/testdata/layers_trace_nodisplayfound.winscope
Binary files differ
diff --git a/services/surfaceflinger/tests/tracing/testdata/transactions_trace_boot.winscope b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_boot.winscope
index 8356ae7..8d03df4 100644
--- a/services/surfaceflinger/tests/tracing/testdata/transactions_trace_boot.winscope
+++ b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_boot.winscope
Binary files differ
diff --git a/services/surfaceflinger/tests/tracing/testdata/transactions_trace_nodisplayfound.winscope b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_nodisplayfound.winscope
index cd62ab8..022861c 100644
--- a/services/surfaceflinger/tests/tracing/testdata/transactions_trace_nodisplayfound.winscope
+++ b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_nodisplayfound.winscope
Binary files differ
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index eac9edc..df3ffd2 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -103,17 +103,18 @@
         "SurfaceFlinger_DestroyDisplayTest.cpp",
         "SurfaceFlinger_DisplayModeSwitching.cpp",
         "SurfaceFlinger_DisplayTransactionCommitTest.cpp",
+        "SurfaceFlinger_ExcludeDolbyVisionTest.cpp",
         "SurfaceFlinger_GetDisplayNativePrimariesTest.cpp",
+        "SurfaceFlinger_HdrOutputControlTest.cpp",
         "SurfaceFlinger_HotplugTest.cpp",
-        "SurfaceFlinger_MultiDisplayLeaderTest.cpp",
+        "SurfaceFlinger_InitializeDisplaysTest.cpp",
+        "SurfaceFlinger_MultiDisplayPacesetterTest.cpp",
         "SurfaceFlinger_NotifyPowerBoostTest.cpp",
-        "SurfaceFlinger_OnInitializeDisplaysTest.cpp",
         "SurfaceFlinger_PowerHintTest.cpp",
         "SurfaceFlinger_SetDisplayStateTest.cpp",
         "SurfaceFlinger_SetPowerModeInternalTest.cpp",
         "SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp",
         "SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp",
-        "SurfaceFlinger_ExcludeDolbyVisionTest.cpp",
         "SchedulerTest.cpp",
         "SetFrameRateTest.cpp",
         "RefreshRateSelectorTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
index 77dc868..ddf3363 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
@@ -17,12 +17,9 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
-#include "FrontEnd/LayerHandle.h"
 #include "FrontEnd/LayerHierarchy.h"
 #include "FrontEnd/LayerLifecycleManager.h"
-#include "Layer.h"
 #include "LayerHierarchyTest.h"
-#include "gui/SurfaceComposerClient.h"
 
 #define UPDATE_AND_VERIFY(HIERARCHY)  \
     ({                                \
@@ -207,7 +204,8 @@
     reparentRelativeLayer(11, 2);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
-    reparentRelativeLayer(11, UNASSIGNED_LAYER_ID);
+    // This calls setLayer
+    removeRelativeZ(11);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
     std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2};
@@ -418,7 +416,7 @@
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
 
     // remove relative parent so layer becomes onscreen again
-    reparentRelativeLayer(11, UNASSIGNED_LAYER_ID);
+    removeRelativeZ(11);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
     expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13};
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
index b9a6159..5b3c7ef 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
@@ -17,11 +17,10 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
-#include "FrontEnd/LayerHandle.h"
+#include "Client.h" // temporarily needed for LayerCreationArgs
+#include "FrontEnd/LayerCreationArgs.h"
 #include "FrontEnd/LayerHierarchy.h"
 #include "FrontEnd/LayerLifecycleManager.h"
-#include "Layer.h"
-#include "gui/SurfaceComposerClient.h"
 
 namespace android::surfaceflinger::frontend {
 
@@ -51,20 +50,21 @@
         createLayer(1221, 122);
     }
 
-    LayerCreationArgs createArgs(uint32_t id, bool canBeRoot, wp<IBinder> parent,
-                                 wp<IBinder> mirror) {
-        LayerCreationArgs args(nullptr, nullptr, "testlayer", 0, {}, std::make_optional(id));
+    LayerCreationArgs createArgs(uint32_t id, bool canBeRoot, uint32_t parentId,
+                                 uint32_t layerIdToMirror) {
+        LayerCreationArgs args(std::make_optional(id));
+        args.name = "testlayer";
         args.addToRoot = canBeRoot;
-        args.parentHandle = parent;
-        args.mirrorLayerHandle = mirror;
+        args.parentId = parentId;
+        args.layerIdToMirror = layerIdToMirror;
         return args;
     }
 
-    LayerCreationArgs createDisplayMirrorArgs(uint32_t id, ui::LayerStack layerStack) {
-        LayerCreationArgs args(nullptr, nullptr, "testlayer", 0, {}, std::make_optional(id));
+    LayerCreationArgs createDisplayMirrorArgs(uint32_t id, ui::LayerStack layerStackToMirror) {
+        LayerCreationArgs args(std::make_optional(id));
+        args.name = "testlayer";
         args.addToRoot = true;
-        args.parentHandle.clear();
-        args.layerStackToMirror = layerStack;
+        args.layerStackToMirror = layerStackToMirror;
         return args;
     }
 
@@ -90,17 +90,14 @@
     }
 
     virtual void createRootLayer(uint32_t id) {
-        sp<LayerHandle> handle = sp<LayerHandle>::make(id);
-        mHandles[id] = handle;
         std::vector<std::unique_ptr<RequestedLayerState>> layers;
         layers.emplace_back(std::make_unique<RequestedLayerState>(
-                createArgs(/*id=*/id, /*canBeRoot=*/true, /*parent=*/nullptr, /*mirror=*/nullptr)));
+                createArgs(/*id=*/id, /*canBeRoot=*/true, /*parent=*/UNASSIGNED_LAYER_ID,
+                           /*mirror=*/UNASSIGNED_LAYER_ID)));
         mLifecycleManager.addLayers(std::move(layers));
     }
 
     void createDisplayMirrorLayer(uint32_t id, ui::LayerStack layerStack) {
-        sp<LayerHandle> handle = sp<LayerHandle>::make(id);
-        mHandles[id] = handle;
         std::vector<std::unique_ptr<RequestedLayerState>> layers;
         layers.emplace_back(std::make_unique<RequestedLayerState>(
                 createDisplayMirrorArgs(/*id=*/id, layerStack)));
@@ -108,62 +105,56 @@
     }
 
     virtual void createLayer(uint32_t id, uint32_t parentId) {
-        sp<LayerHandle> handle = sp<LayerHandle>::make(id);
-        mHandles[id] = handle;
         std::vector<std::unique_ptr<RequestedLayerState>> layers;
         layers.emplace_back(std::make_unique<RequestedLayerState>(
-                createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/mHandles[parentId],
-                           /*mirror=*/nullptr)));
+                createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/parentId,
+                           /*mirror=*/UNASSIGNED_LAYER_ID)));
         mLifecycleManager.addLayers(std::move(layers));
     }
 
-    void reparentLayer(uint32_t id, uint32_t newParentId) {
+    std::vector<TransactionState> reparentLayerTransaction(uint32_t id, uint32_t newParentId) {
         std::vector<TransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
-
-        if (newParentId == UNASSIGNED_LAYER_ID) {
-            transactions.back().states.front().state.parentSurfaceControlForChild = nullptr;
-        } else {
-            auto parentHandle = mHandles[newParentId];
-            transactions.back().states.front().state.parentSurfaceControlForChild =
-                    sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(), parentHandle,
-                                             static_cast<int32_t>(newParentId), "Test");
-        }
+        transactions.back().states.front().parentId = newParentId;
         transactions.back().states.front().state.what = layer_state_t::eReparent;
-        transactions.back().states.front().state.surface = mHandles[id];
-        mLifecycleManager.applyTransactions(transactions);
+        transactions.back().states.front().relativeParentId = UNASSIGNED_LAYER_ID;
+        transactions.back().states.front().layerId = id;
+        return transactions;
+    }
+
+    void reparentLayer(uint32_t id, uint32_t newParentId) {
+        mLifecycleManager.applyTransactions(reparentLayerTransaction(id, newParentId));
+    }
+
+    std::vector<TransactionState> relativeLayerTransaction(uint32_t id, uint32_t relativeParentId) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+        transactions.back().states.front().relativeParentId = relativeParentId;
+        transactions.back().states.front().state.what = layer_state_t::eRelativeLayerChanged;
+        transactions.back().states.front().layerId = id;
+        return transactions;
     }
 
     void reparentRelativeLayer(uint32_t id, uint32_t relativeParentId) {
+        mLifecycleManager.applyTransactions(relativeLayerTransaction(id, relativeParentId));
+    }
+
+    void removeRelativeZ(uint32_t id) {
         std::vector<TransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
-
-        if (relativeParentId == UNASSIGNED_LAYER_ID) {
-            transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
-        } else {
-            auto parentHandle = mHandles[relativeParentId];
-            transactions.back().states.front().state.relativeLayerSurfaceControl =
-                    sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(), parentHandle,
-                                             static_cast<int32_t>(relativeParentId), "test");
-            transactions.back().states.front().state.what = layer_state_t::eRelativeLayerChanged;
-        }
-        transactions.back().states.front().state.surface = mHandles[id];
+        transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
+        transactions.back().states.front().layerId = id;
         mLifecycleManager.applyTransactions(transactions);
     }
 
-    virtual void mirrorLayer(uint32_t id, uint32_t parent, uint32_t layerToMirror) {
-        auto parentHandle = (parent == UNASSIGNED_LAYER_ID) ? nullptr : mHandles[parent];
-        auto mirrorHandle =
-                (layerToMirror == UNASSIGNED_LAYER_ID) ? nullptr : mHandles[layerToMirror];
-
-        sp<LayerHandle> handle = sp<LayerHandle>::make(id);
-        mHandles[id] = handle;
+    virtual void mirrorLayer(uint32_t id, uint32_t parentId, uint32_t layerIdToMirror) {
         std::vector<std::unique_ptr<RequestedLayerState>> layers;
         layers.emplace_back(std::make_unique<RequestedLayerState>(
-                createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/parentHandle,
-                           /*mirror=*/mHandles[layerToMirror])));
+                createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/parentId,
+                           /*mirror=*/layerIdToMirror)));
         mLifecycleManager.addLayers(std::move(layers));
     }
 
@@ -173,7 +164,7 @@
         transactions.back().states.push_back({});
         transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
         transactions.back().states.front().state.bgColor.a = alpha;
-        transactions.back().states.front().state.surface = mHandles[id];
+        transactions.back().states.front().layerId = id;
         mLifecycleManager.applyTransactions(transactions);
     }
 
@@ -196,16 +187,19 @@
                 mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
     }
 
-    void setZ(uint32_t id, int32_t z) {
+    std::vector<TransactionState> setZTransaction(uint32_t id, int32_t z) {
         std::vector<TransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
         transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
-        transactions.back().states.front().state.surface = mHandles[id];
-        transactions.back().states.front().state.layerId = static_cast<int32_t>(id);
+        transactions.back().states.front().layerId = id;
         transactions.back().states.front().state.z = z;
-        mLifecycleManager.applyTransactions(transactions);
+        return transactions;
+    }
+
+    void setZ(uint32_t id, int32_t z) {
+        mLifecycleManager.applyTransactions(setZTransaction(id, z));
     }
 
     void setCrop(uint32_t id, const Rect& crop) {
@@ -214,8 +208,7 @@
         transactions.back().states.push_back({});
 
         transactions.back().states.front().state.what = layer_state_t::eCropChanged;
-        transactions.back().states.front().state.surface = mHandles[id];
-        transactions.back().states.front().state.layerId = static_cast<int32_t>(id);
+        transactions.back().states.front().layerId = id;
         transactions.back().states.front().state.crop = crop;
         mLifecycleManager.applyTransactions(transactions);
     }
@@ -228,8 +221,7 @@
         transactions.back().states.front().state.what = layer_state_t::eFlagsChanged;
         transactions.back().states.front().state.flags = flags;
         transactions.back().states.front().state.mask = mask;
-        transactions.back().states.front().state.surface = mHandles[id];
-        transactions.back().states.front().state.layerId = static_cast<int32_t>(id);
+        transactions.back().states.front().layerId = id;
         mLifecycleManager.applyTransactions(transactions);
     }
 
@@ -239,8 +231,7 @@
         transactions.back().states.push_back({});
 
         transactions.back().states.front().state.what = layer_state_t::eAlphaChanged;
-        transactions.back().states.front().state.surface = mHandles[id];
-        transactions.back().states.front().state.layerId = static_cast<int32_t>(id);
+        transactions.back().states.front().layerId = id;
         transactions.back().states.front().state.color.a = static_cast<half>(alpha);
         mLifecycleManager.applyTransactions(transactions);
     }
@@ -257,8 +248,7 @@
         transactions.back().states.push_back({});
         transactions.back().states.front().state.what = layer_state_t::eColorChanged;
         transactions.back().states.front().state.color.rgb = rgb;
-        transactions.back().states.front().state.surface = mHandles[id];
-        transactions.back().states.front().state.layerId = static_cast<int32_t>(id);
+        transactions.back().states.front().layerId = id;
         mLifecycleManager.applyTransactions(transactions);
     }
 
@@ -268,8 +258,7 @@
         transactions.back().states.push_back({});
 
         transactions.back().states.front().state.what = layer_state_t::eLayerStackChanged;
-        transactions.back().states.front().state.surface = mHandles[id];
-        transactions.back().states.front().state.layerId = static_cast<int32_t>(id);
+        transactions.back().states.front().layerId = id;
         transactions.back().states.front().state.layerStack = ui::LayerStack::fromValue(layerStack);
         mLifecycleManager.applyTransactions(transactions);
     }
@@ -280,8 +269,7 @@
         transactions.back().states.push_back({});
 
         transactions.back().states.front().state.what = layer_state_t::eInputInfoChanged;
-        transactions.back().states.front().state.surface = mHandles[id];
-        transactions.back().states.front().state.layerId = static_cast<int32_t>(id);
+        transactions.back().states.front().layerId = id;
         transactions.back().states.front().state.windowInfoHandle =
                 sp<gui::WindowInfoHandle>::make();
         auto inputInfo = transactions.back().states.front().state.windowInfoHandle->editInfo();
@@ -291,7 +279,6 @@
     }
 
     LayerLifecycleManager mLifecycleManager;
-    std::unordered_map<uint32_t, sp<LayerHandle>> mHandles;
 };
 
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 8397f8d..b767276 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -112,7 +112,8 @@
                                Fps desiredRefreshRate, int numFrames) {
         LayerHistory::Summary summary;
         for (int i = 0; i < numFrames; i++) {
-            history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+            history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                             LayerHistory::LayerUpdateType::Buffer);
             time += frameRate.getPeriodNsecs();
 
             summary = summarizeLayerHistory(time);
@@ -155,7 +156,8 @@
     EXPECT_TRUE(summarizeLayerHistory(time).empty());
     EXPECT_EQ(0, activeLayerCount());
 
-    history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
+    history().record(layer->getSequence(), layer->getLayerProps(), 0, time,
+                     LayerHistory::LayerUpdateType::Buffer);
     history().setDefaultFrameRateCompatibility(layer.get(), true /* contentDetectionEnabled */);
 
     EXPECT_TRUE(summarizeLayerHistory(time).empty());
@@ -177,7 +179,8 @@
     EXPECT_TRUE(summarizeLayerHistory(time).empty());
     EXPECT_EQ(0, activeLayerCount());
 
-    history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
+    history().record(layer->getSequence(), layer->getLayerProps(), 0, time,
+                     LayerHistory::LayerUpdateType::Buffer);
     history().setDefaultFrameRateCompatibility(layer.get(), true /* contentDetectionEnabled */);
 
     auto summary = summarizeLayerHistory(time);
@@ -205,7 +208,8 @@
 
     // Max returned if active layers have insufficient history.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
-        history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
+        history().record(layer->getSequence(), layer->getLayerProps(), 0, time,
+                         LayerHistory::LayerUpdateType::Buffer);
         ASSERT_EQ(1, summarizeLayerHistory(time).size());
         EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
         EXPECT_EQ(1, activeLayerCount());
@@ -214,7 +218,8 @@
 
     // Max is returned since we have enough history but there is no timestamp votes.
     for (int i = 0; i < 10; i++) {
-        history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
+        history().record(layer->getSequence(), layer->getLayerProps(), 0, time,
+                         LayerHistory::LayerUpdateType::Buffer);
         ASSERT_EQ(1, summarizeLayerHistory(time).size());
         EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
         EXPECT_EQ(1, activeLayerCount());
@@ -232,7 +237,8 @@
 
     nsecs_t time = systemTime();
 
-    history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
+    history().record(layer->getSequence(), layer->getLayerProps(), 0, time,
+                     LayerHistory::LayerUpdateType::Buffer);
     auto summary = summarizeLayerHistory(time);
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     // Layer is still considered inactive so we expect to get Min
@@ -240,7 +246,8 @@
     EXPECT_EQ(1, activeLayerCount());
 
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(false));
-    history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
+    history().record(layer->getSequence(), layer->getLayerProps(), 0, time,
+                     LayerHistory::LayerUpdateType::Buffer);
 
     summary = summarizeLayerHistory(time);
     EXPECT_TRUE(summarizeLayerHistory(time).empty());
@@ -257,7 +264,8 @@
 
     nsecs_t time = systemTime();
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
         time += LO_FPS_PERIOD;
     }
 
@@ -280,7 +288,8 @@
 
     nsecs_t time = systemTime();
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
         time += HI_FPS_PERIOD;
     }
 
@@ -307,7 +316,8 @@
 
     nsecs_t time = systemTime();
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
         time += HI_FPS_PERIOD;
     }
 
@@ -335,7 +345,8 @@
 
     nsecs_t time = systemTime();
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
         time += LO_FPS_PERIOD;
     }
 
@@ -363,7 +374,8 @@
 
     nsecs_t time = systemTime();
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
         time += HI_FPS_PERIOD;
     }
 
@@ -395,7 +407,8 @@
 
     nsecs_t time = systemTime();
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
         time += HI_FPS_PERIOD;
     }
 
@@ -441,7 +454,8 @@
 
     // layer1 is active but infrequent.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        history().record(layer1->getSequence(), layer1->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
         time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
         summary = summarizeLayerHistory(time);
     }
@@ -453,13 +467,15 @@
 
     // layer2 is frequent and has high refresh rate.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        history().record(layer2->getSequence(), layer2->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
         time += HI_FPS_PERIOD;
         summary = summarizeLayerHistory(time);
     }
 
     // layer1 is still active but infrequent.
-    history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    history().record(layer1->getSequence(), layer1->getLayerProps(), time, time,
+                     LayerHistory::LayerUpdateType::Buffer);
 
     ASSERT_EQ(2, summary.size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
@@ -472,7 +488,8 @@
     // layer1 is no longer active.
     // layer2 is frequent and has low refresh rate.
     for (int i = 0; i < 2 * PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        history().record(layer2->getSequence(), layer2->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
         time += LO_FPS_PERIOD;
         summary = summarizeLayerHistory(time);
     }
@@ -488,10 +505,12 @@
     constexpr int RATIO = LO_FPS_PERIOD / HI_FPS_PERIOD;
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
         if (i % RATIO == 0) {
-            history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+            history().record(layer2->getSequence(), layer2->getLayerProps(), time, time,
+                             LayerHistory::LayerUpdateType::Buffer);
         }
 
-        history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        history().record(layer3->getSequence(), layer3->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
         time += HI_FPS_PERIOD;
         summary = summarizeLayerHistory(time);
     }
@@ -504,7 +523,8 @@
     EXPECT_EQ(2, frequentLayerCount(time));
 
     // layer3 becomes recently active.
-    history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    history().record(layer3->getSequence(), layer3->getLayerProps(), time, time,
+                     LayerHistory::LayerUpdateType::Buffer);
     summary = summarizeLayerHistory(time);
     ASSERT_EQ(2, summary.size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
@@ -530,7 +550,8 @@
     // layer2 still has low refresh rate.
     // layer3 becomes inactive.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        history().record(layer2->getSequence(), layer2->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
         time += LO_FPS_PERIOD;
         summary = summarizeLayerHistory(time);
     }
@@ -551,7 +572,8 @@
 
     // layer3 becomes active and has high refresh rate.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE + FREQUENT_LAYER_WINDOW_SIZE + 1; i++) {
-        history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        history().record(layer3->getSequence(), layer3->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
         time += HI_FPS_PERIOD;
         summary = summarizeLayerHistory(time);
     }
@@ -582,7 +604,8 @@
 
     // the very first updates makes the layer frequent
     for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
-        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
         time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
 
         EXPECT_EQ(1, layerCount());
@@ -593,7 +616,8 @@
     }
 
     // the next update with the MAX_FREQUENT_LAYER_PERIOD_NS will get us to infrequent
-    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                     LayerHistory::LayerUpdateType::Buffer);
     time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
 
     EXPECT_EQ(1, layerCount());
@@ -607,7 +631,8 @@
 
     // Now even if we post a quick few frame we should stay infrequent
     for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
-        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
         time += HI_FPS_PERIOD;
 
         EXPECT_EQ(1, layerCount());
@@ -618,7 +643,8 @@
     }
 
     // More quick frames will get us to frequent again
-    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                     LayerHistory::LayerUpdateType::Buffer);
     time += HI_FPS_PERIOD;
 
     EXPECT_EQ(1, layerCount());
@@ -645,9 +671,10 @@
     nsecs_t time = systemTime();
 
     // Post a buffer to the layers to make them active
-    history().record(explicitVisiblelayer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-    history().record(explicitInvisiblelayer.get(), time, time,
-                     LayerHistory::LayerUpdateType::Buffer);
+    history().record(explicitVisiblelayer->getSequence(), explicitVisiblelayer->getLayerProps(),
+                     time, time, LayerHistory::LayerUpdateType::Buffer);
+    history().record(explicitInvisiblelayer->getSequence(), explicitInvisiblelayer->getLayerProps(),
+                     time, time, LayerHistory::LayerUpdateType::Buffer);
 
     EXPECT_EQ(2, layerCount());
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
@@ -673,7 +700,8 @@
 
     // layer is active but infrequent.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
         time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
     }
 
@@ -684,7 +712,8 @@
     EXPECT_EQ(0, animatingLayerCount(time));
 
     // another update with the same cadence keep in infrequent
-    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                     LayerHistory::LayerUpdateType::Buffer);
     time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
 
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
@@ -694,7 +723,8 @@
     EXPECT_EQ(0, animatingLayerCount(time));
 
     // an update as animation will immediately vote for Max
-    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::AnimationTX);
+    history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                     LayerHistory::LayerUpdateType::AnimationTX);
     time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
 
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
@@ -719,7 +749,8 @@
 
     // Fill up the window with frequent updates
     for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE; i++) {
-        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
         time += (60_Hz).getPeriodNsecs();
 
         EXPECT_EQ(1, layerCount());
@@ -731,7 +762,8 @@
 
     // posting a buffer after long inactivity should retain the layer as active
     time += std::chrono::nanoseconds(3s).count();
-    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                     LayerHistory::LayerUpdateType::Buffer);
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summarizeLayerHistory(time)[0].vote);
     EXPECT_EQ(60_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
@@ -741,9 +773,11 @@
 
     // posting more infrequent buffer should make the layer infrequent
     time += (MAX_FREQUENT_LAYER_PERIOD_NS + 1ms).count();
-    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                     LayerHistory::LayerUpdateType::Buffer);
     time += (MAX_FREQUENT_LAYER_PERIOD_NS + 1ms).count();
-    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                     LayerHistory::LayerUpdateType::Buffer);
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
     EXPECT_EQ(1, activeLayerCount());
@@ -751,7 +785,8 @@
     EXPECT_EQ(0, animatingLayerCount(time));
 
     // posting another buffer should keep the layer infrequent
-    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                     LayerHistory::LayerUpdateType::Buffer);
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
     EXPECT_EQ(1, activeLayerCount());
@@ -759,8 +794,10 @@
     EXPECT_EQ(0, animatingLayerCount(time));
 
     // posting more buffers would mean starting of an animation, so making the layer frequent
-    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                     LayerHistory::LayerUpdateType::Buffer);
+    history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                     LayerHistory::LayerUpdateType::Buffer);
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
     EXPECT_EQ(1, activeLayerCount());
@@ -769,7 +806,8 @@
 
     // posting a buffer after long inactivity should retain the layer as active
     time += std::chrono::nanoseconds(3s).count();
-    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                     LayerHistory::LayerUpdateType::Buffer);
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
     EXPECT_EQ(1, activeLayerCount());
@@ -778,7 +816,8 @@
 
     // posting another buffer should keep the layer frequent
     time += (60_Hz).getPeriodNsecs();
-    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                     LayerHistory::LayerUpdateType::Buffer);
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
     EXPECT_EQ(1, activeLayerCount());
@@ -801,7 +840,8 @@
 
     // layer is active but infrequent.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
         time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
     }
 
@@ -869,10 +909,10 @@
     const nsecs_t startTime = systemTime();
 
     const std::chrono::nanoseconds heuristicUpdateDelta = 41'666'667ns;
-    history().record(heuristicLayer.get(), startTime, startTime,
-                     LayerHistory::LayerUpdateType::Buffer);
-    history().record(infrequentLayer.get(), startTime, startTime,
-                     LayerHistory::LayerUpdateType::Buffer);
+    history().record(heuristicLayer->getSequence(), heuristicLayer->getLayerProps(), startTime,
+                     startTime, LayerHistory::LayerUpdateType::Buffer);
+    history().record(infrequentLayer->getSequence(), heuristicLayer->getLayerProps(), startTime,
+                     startTime, LayerHistory::LayerUpdateType::Buffer);
 
     nsecs_t time = startTime;
     nsecs_t lastInfrequentUpdate = startTime;
@@ -880,14 +920,15 @@
     int infrequentLayerUpdates = 0;
     while (infrequentLayerUpdates <= totalInfrequentLayerUpdates) {
         time += heuristicUpdateDelta.count();
-        history().record(heuristicLayer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        history().record(heuristicLayer->getSequence(), heuristicLayer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
 
         if (time - lastInfrequentUpdate >= infrequentUpdateDelta.count()) {
             ALOGI("submitting infrequent frame [%d/%d]", infrequentLayerUpdates,
                   totalInfrequentLayerUpdates);
             lastInfrequentUpdate = time;
-            history().record(infrequentLayer.get(), time, time,
-                             LayerHistory::LayerUpdateType::Buffer);
+            history().record(infrequentLayer->getSequence(), infrequentLayer->getLayerProps(), time,
+                             time, LayerHistory::LayerUpdateType::Buffer);
             infrequentLayerUpdates++;
         }
 
diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
index 99c1d23..14b8e4b 100644
--- a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
@@ -17,25 +17,14 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
-#include "FrontEnd/LayerHandle.h"
 #include "FrontEnd/LayerLifecycleManager.h"
-#include "Layer.h"
-#include "gui/SurfaceComposerClient.h"
+#include "LayerHierarchyTest.h"
+#include "TransactionState.h"
 
 using namespace android::surfaceflinger;
 
 namespace android::surfaceflinger::frontend {
 
-namespace {
-LayerCreationArgs createArgs(uint32_t id, bool canBeRoot, wp<IBinder> parent, wp<IBinder> mirror) {
-    LayerCreationArgs args(nullptr, nullptr, "testlayer", 0, {}, std::make_optional(id));
-    args.addToRoot = canBeRoot;
-    args.parentHandle = parent;
-    args.mirrorLayerHandle = mirror;
-    return args;
-}
-} // namespace
-
 // To run test:
 /**
  mp :libsurfaceflinger_unittest && adb sync; adb shell \
@@ -66,69 +55,24 @@
     std::unordered_set<uint32_t> mActualLayersDestroyed;
 };
 
-class LayerLifecycleManagerTest : public testing::Test {
+class LayerLifecycleManagerTest : public LayerHierarchyTestBase {
 protected:
     std::unique_ptr<RequestedLayerState> rootLayer(uint32_t id) {
-        return std::make_unique<RequestedLayerState>(
-                createArgs(/*id=*/id, /*canBeRoot=*/true, /*parent=*/nullptr, /*mirror=*/nullptr));
+        return std::make_unique<RequestedLayerState>(createArgs(/*id=*/id, /*canBeRoot=*/true,
+                                                                /*parent=*/UNASSIGNED_LAYER_ID,
+                                                                /*mirror=*/UNASSIGNED_LAYER_ID));
     }
 
     std::unique_ptr<RequestedLayerState> childLayer(uint32_t id, uint32_t parentId) {
-        mHandles[parentId] = sp<LayerHandle>::make(parentId);
         return std::make_unique<RequestedLayerState>(createArgs(/*id=*/id, /*canBeRoot=*/false,
-                                                                /*parent=*/mHandles[parentId],
-                                                                /*mirror=*/nullptr));
-    }
-
-    TransactionState reparentLayer(uint32_t id, uint32_t newParentId) {
-        TransactionState transaction;
-        transaction.states.push_back({});
-
-        if (newParentId == UNASSIGNED_LAYER_ID) {
-            transaction.states.front().state.parentSurfaceControlForChild = nullptr;
-        } else {
-            transaction.states.front().state.parentSurfaceControlForChild =
-                    sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(),
-                                             sp<LayerHandle>::make(newParentId),
-                                             static_cast<int32_t>(newParentId), "Test");
-        }
-        transaction.states.front().state.what = layer_state_t::eReparent;
-        transaction.states.front().state.surface = sp<LayerHandle>::make(id);
-        return transaction;
-    }
-
-    TransactionState setLayer(uint32_t id, int32_t z) {
-        TransactionState transaction;
-        transaction.states.push_back({});
-        transaction.states.front().state.z = z;
-        transaction.states.front().state.what = layer_state_t::eLayerChanged;
-        transaction.states.front().state.surface = sp<LayerHandle>::make(id);
-        return transaction;
-    }
-
-    TransactionState makeRelative(uint32_t id, uint32_t relativeParentId) {
-        TransactionState transaction;
-        transaction.states.push_back({});
-
-        if (relativeParentId == UNASSIGNED_LAYER_ID) {
-            transaction.states.front().state.relativeLayerSurfaceControl = nullptr;
-        } else {
-            transaction.states.front().state.relativeLayerSurfaceControl =
-                    sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(),
-                                             sp<LayerHandle>::make(relativeParentId),
-                                             static_cast<int32_t>(relativeParentId), "Test");
-        }
-        transaction.states.front().state.what = layer_state_t::eRelativeLayerChanged;
-        transaction.states.front().state.surface = sp<LayerHandle>::make(id);
-        return transaction;
+                                                                parentId,
+                                                                /*mirror=*/UNASSIGNED_LAYER_ID));
     }
 
     RequestedLayerState* getRequestedLayerState(LayerLifecycleManager& lifecycleManager,
                                                 uint32_t layerId) {
         return lifecycleManager.getLayerFromId(layerId);
     }
-
-    std::unordered_map<uint32_t, sp<LayerHandle>> mHandles;
 };
 
 TEST_F(LayerLifecycleManagerTest, addLayers) {
@@ -153,16 +97,7 @@
     std::vector<std::unique_ptr<RequestedLayerState>> layers;
     layers.emplace_back(rootLayer(1));
     lifecycleManager.addLayers(std::move(layers));
-
-    std::vector<TransactionState> transactions;
-    transactions.emplace_back();
-    transactions.back().states.push_back({});
-    transactions.back().states.front().state.z = 2;
-    transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
-    sp<LayerHandle> handle = sp<LayerHandle>::make(1u);
-    transactions.back().states.front().state.surface = handle;
-    lifecycleManager.applyTransactions(transactions);
-    transactions.clear();
+    lifecycleManager.applyTransactions(setZTransaction(1, 2));
 
     auto& managedLayers = lifecycleManager.getLayers();
     ASSERT_EQ(managedLayers.size(), 1u);
@@ -177,11 +112,12 @@
     EXPECT_FALSE(managedLayers.front()->changes.test(RequestedLayerState::Changes::Z));
 
     // apply transactions that do not affect the hierarchy
+    std::vector<TransactionState> transactions;
     transactions.emplace_back();
     transactions.back().states.push_back({});
     transactions.back().states.front().state.backgroundBlurRadius = 22;
     transactions.back().states.front().state.what = layer_state_t::eBackgroundBlurRadiusChanged;
-    transactions.back().states.front().state.surface = handle;
+    transactions.back().states.front().layerId = 1;
     lifecycleManager.applyTransactions(transactions);
     EXPECT_FALSE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
     lifecycleManager.commitChanges();
@@ -232,7 +168,7 @@
     listener->expectLayersAdded({1, 2, 3});
     listener->expectLayersDestroyed({});
 
-    lifecycleManager.applyTransactions({reparentLayer(3, UNASSIGNED_LAYER_ID)});
+    lifecycleManager.applyTransactions(reparentLayerTransaction(3, UNASSIGNED_LAYER_ID));
     lifecycleManager.commitChanges();
     listener->expectLayersAdded({});
     listener->expectLayersDestroyed({});
@@ -257,7 +193,7 @@
     listener->expectLayersAdded({1, 2, 3, 4});
     listener->expectLayersDestroyed({});
 
-    lifecycleManager.applyTransactions({reparentLayer(3, UNASSIGNED_LAYER_ID)});
+    lifecycleManager.applyTransactions(reparentLayerTransaction(3, UNASSIGNED_LAYER_ID));
     lifecycleManager.onHandlesDestroyed({3});
     lifecycleManager.commitChanges();
     listener->expectLayersAdded({});
@@ -278,7 +214,7 @@
     listener->expectLayersAdded({1, 2, 3, 4});
     listener->expectLayersDestroyed({});
 
-    lifecycleManager.applyTransactions({reparentLayer(3, UNASSIGNED_LAYER_ID)});
+    lifecycleManager.applyTransactions(reparentLayerTransaction(3, UNASSIGNED_LAYER_ID));
     lifecycleManager.onHandlesDestroyed({3, 4});
     lifecycleManager.commitChanges();
     listener->expectLayersAdded({});
@@ -300,9 +236,9 @@
     listener->expectLayersAdded({1, 2, 3, 4});
     listener->expectLayersDestroyed({});
 
-    lifecycleManager.applyTransactions({makeRelative(4, 1)});
+    lifecycleManager.applyTransactions(relativeLayerTransaction(4, 1));
     EXPECT_TRUE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
-    lifecycleManager.applyTransactions({reparentLayer(4, 2)});
+    lifecycleManager.applyTransactions(reparentLayerTransaction(4, 2));
     EXPECT_TRUE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
 
     lifecycleManager.commitChanges();
@@ -325,9 +261,9 @@
     listener->expectLayersAdded({1, 2, 3, 4});
     listener->expectLayersDestroyed({});
 
-    lifecycleManager.applyTransactions({makeRelative(4, 1)});
+    lifecycleManager.applyTransactions(relativeLayerTransaction(4, 1));
     EXPECT_TRUE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
-    lifecycleManager.applyTransactions({reparentLayer(4, UNASSIGNED_LAYER_ID)});
+    lifecycleManager.applyTransactions(reparentLayerTransaction(4, UNASSIGNED_LAYER_ID));
     EXPECT_FALSE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
 
     lifecycleManager.commitChanges();
@@ -350,9 +286,9 @@
     listener->expectLayersAdded({1, 2, 3, 4});
     listener->expectLayersDestroyed({});
 
-    lifecycleManager.applyTransactions({makeRelative(4, 1)});
+    lifecycleManager.applyTransactions(relativeLayerTransaction(4, 1));
     EXPECT_TRUE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
-    lifecycleManager.applyTransactions({setLayer(4, 1)});
+    lifecycleManager.applyTransactions(setZTransaction(4, 1));
     EXPECT_FALSE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
 
     lifecycleManager.commitChanges();
@@ -374,8 +310,7 @@
     transactions.back().states.push_back({});
     transactions.back().states.front().state.bgColor.a = 0.5;
     transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
-    sp<LayerHandle> handle = sp<LayerHandle>::make(1u);
-    transactions.back().states.front().state.surface = handle;
+    transactions.back().states.front().layerId = 1;
     lifecycleManager.applyTransactions(transactions);
 
     auto& managedLayers = lifecycleManager.getLayers();
@@ -403,13 +338,12 @@
     transactions.back().states.push_back({});
     transactions.back().states.front().state.bgColor.a = 0.5;
     transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
-    sp<LayerHandle> handle = sp<LayerHandle>::make(1u);
-    transactions.back().states.front().state.surface = handle;
+    transactions.back().states.front().layerId = 1;
     transactions.emplace_back();
     transactions.back().states.push_back({});
     transactions.back().states.front().state.bgColor.a = 0;
     transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
-    transactions.back().states.front().state.surface = handle;
+    transactions.back().states.front().layerId = 1;
 
     lifecycleManager.applyTransactions(transactions);
 
@@ -437,8 +371,7 @@
     transactions.back().states.push_back({});
     transactions.back().states.front().state.bgColor.a = 0.5;
     transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
-    sp<LayerHandle> handle = sp<LayerHandle>::make(1u);
-    transactions.back().states.front().state.surface = handle;
+    transactions.back().states.front().layerId = 1;
     transactions.emplace_back();
     lifecycleManager.applyTransactions(transactions);
     lifecycleManager.onHandlesDestroyed({1});
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index db0b907..b8c4781 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -17,11 +17,9 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
-#include "FrontEnd/LayerHandle.h"
 #include "FrontEnd/LayerHierarchy.h"
 #include "FrontEnd/LayerLifecycleManager.h"
 #include "FrontEnd/LayerSnapshotBuilder.h"
-#include "Layer.h"
 #include "LayerHierarchyTest.h"
 
 #define UPDATE_AND_VERIFY(BUILDER, ...)                                    \
@@ -268,7 +266,7 @@
     transactions.back().states.front().state.what = layer_state_t::eMetadataChanged;
     transactions.back().states.front().state.metadata = LayerMetadata();
     transactions.back().states.front().state.metadata.setInt32(METADATA_GAME_MODE, 42);
-    transactions.back().states.front().state.surface = mHandles[1];
+    transactions.back().states.front().layerId = 1;
     transactions.back().states.front().state.layerId = static_cast<int32_t>(1);
     mLifecycleManager.applyTransactions(transactions);
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
@@ -297,8 +295,7 @@
             ANATIVEWINDOW_FRAME_RATE_EXACT;
     transactions.back().states.front().state.changeFrameRateStrategy =
             ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS;
-    transactions.back().states.front().state.surface = mHandles[11];
-    transactions.back().states.front().state.layerId = static_cast<int32_t>(11);
+    transactions.back().states.front().layerId = 11;
     mLifecycleManager.applyTransactions(transactions);
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
 
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index f4d052d..63ed87b 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -1455,6 +1455,24 @@
         lr.name = "ExplicitExactOrMultiple 29.97 Hz";
         EXPECT_EQ(kModeId60Frac, selector.getBestFrameRateMode(layers)->getId());
     }
+
+    // Test that 29.97 will choose 30 if 59.94 is not supported
+    {
+        auto selector = createSelector(makeModes(kMode30, kMode60), kModeId60);
+
+        lr.desiredRefreshRate = 29.97_Hz;
+        lr.name = "ExplicitExactOrMultiple 29.97 Hz";
+        EXPECT_EQ(kModeId30, selector.getBestFrameRateMode(layers)->getId());
+    }
+
+    // Test that 59.94 will choose 60 if 59.94 is not supported
+    {
+        auto selector = createSelector(makeModes(kMode60, kMode30Frac, kMode30), kModeId60);
+
+        lr.desiredRefreshRate = 59.94_Hz;
+        lr.name = "ExplicitExactOrMultiple 59.94 Hz";
+        EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
+    }
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ExplicitExact_WithFractionalRefreshRates) {
@@ -2981,6 +2999,12 @@
     layers[0].name = "Test layer";
     layers[0].vote = LayerVoteType::Min;
     EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+
+    constexpr FpsRanges kCappedAt60 = {{30_Hz, 90_Hz}, {30_Hz, 60_Hz}};
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {DisplayModeId(kModeId60), kCappedAt60, kCappedAt60}));
+    EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
 }
 
 TEST_P(RefreshRateSelectorTest, frameRateIsCappedByPolicy) {
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 26281d2..dc76b4c 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -161,7 +161,8 @@
 
     // recordLayerHistory should be a noop
     ASSERT_EQ(0u, mScheduler->getNumActiveLayers());
-    mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer);
+    mScheduler->recordLayerHistory(layer->getSequence(), layer->getLayerProps(), 0,
+                                   LayerHistory::LayerUpdateType::Buffer);
     ASSERT_EQ(0u, mScheduler->getNumActiveLayers());
 
     constexpr hal::PowerMode kPowerModeOn = hal::PowerMode::ON;
@@ -185,7 +186,8 @@
                                                                       kDisplay1Mode60->getId()));
 
     ASSERT_EQ(0u, mScheduler->getNumActiveLayers());
-    mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer);
+    mScheduler->recordLayerHistory(layer->getSequence(), layer->getLayerProps(), 0,
+                                   LayerHistory::LayerUpdateType::Buffer);
     ASSERT_EQ(1u, mScheduler->getNumActiveLayers());
 }
 
@@ -234,7 +236,8 @@
     const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
     EXPECT_CALL(*layer, isVisible()).WillOnce(Return(true));
 
-    mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer);
+    mScheduler->recordLayerHistory(layer->getSequence(), layer->getLayerProps(), 0,
+                                   LayerHistory::LayerUpdateType::Buffer);
 
     constexpr hal::PowerMode kPowerModeOn = hal::PowerMode::ON;
     FTL_FAKE_GUARD(kMainThreadContext, mScheduler->setDisplayPowerMode(kDisplayId1, kPowerModeOn));
diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
index 6adcd52..44ab569 100644
--- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
@@ -380,8 +380,10 @@
     commitTransaction();
 
     auto& history = mFlinger.mutableScheduler().mutableLayerHistory();
-    history.record(parent.get(), 0, 0, LayerHistory::LayerUpdateType::Buffer);
-    history.record(child.get(), 0, 0, LayerHistory::LayerUpdateType::Buffer);
+    history.record(parent->getSequence(), parent->getLayerProps(), 0, 0,
+                   LayerHistory::LayerUpdateType::Buffer);
+    history.record(child->getSequence(), child->getLayerProps(), 0, 0,
+                   LayerHistory::LayerUpdateType::Buffer);
 
     const auto selectorPtr = mFlinger.mutableScheduler().refreshRateSelector();
     const auto summary = history.summarize(*selectorPtr, 0);
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index fd1fd47..e176546 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -119,7 +119,7 @@
     ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
     ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
 
-    mFlinger.onActiveDisplayChanged(*mDisplay);
+    mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
 
     mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
                                         mock::createDisplayModeSpecs(kModeId90.value(), false, 0,
@@ -159,7 +159,7 @@
 
     ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
 
-    mFlinger.onActiveDisplayChanged(*mDisplay);
+    mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
 
     mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
                                         mock::createDisplayModeSpecs(kModeId90.value(), true, 0,
@@ -195,7 +195,7 @@
     ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
     ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
 
-    mFlinger.onActiveDisplayChanged(*mDisplay);
+    mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
 
     mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
                                         mock::createDisplayModeSpecs(kModeId90.value(), false, 0,
@@ -238,7 +238,7 @@
     ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
     ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
 
-    mFlinger.onActiveDisplayChanged(*mDisplay);
+    mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
 
     mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
                                         mock::createDisplayModeSpecs(kModeId90_4K.value(), false, 0,
@@ -284,9 +284,43 @@
     ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90_4K);
 }
 
-TEST_F(DisplayModeSwitchingTest, multiDisplay) {
+MATCHER_P2(ModeSwitchingTo, flinger, modeId, "") {
+    if (!arg->getDesiredActiveMode()) {
+        *result_listener << "No desired active mode";
+        return false;
+    }
+
+    if (arg->getDesiredActiveMode()->modeOpt->modePtr->getId() != modeId) {
+        *result_listener << "Unexpected desired active mode " << modeId;
+        return false;
+    }
+
+    if (!flinger->scheduler()->vsyncModulator().isVsyncConfigEarly()) {
+        *result_listener << "VsyncModulator did not shift to early phase";
+        return false;
+    }
+
+    return true;
+}
+
+MATCHER_P(ModeSettledTo, modeId, "") {
+    if (const auto desiredOpt = arg->getDesiredActiveMode()) {
+        *result_listener << "Unsettled desired active mode "
+                         << desiredOpt->modeOpt->modePtr->getId();
+        return false;
+    }
+
     ftl::FakeGuard guard(kMainThreadContext);
 
+    if (arg->getActiveMode().modePtr->getId() != modeId) {
+        *result_listener << "Settled to unexpected active mode " << modeId;
+        return false;
+    }
+
+    return true;
+}
+
+TEST_F(DisplayModeSwitchingTest, multiDisplay) {
     constexpr HWDisplayId kInnerDisplayHwcId = PrimaryDisplayVariant::HWC_DISPLAY_ID;
     constexpr HWDisplayId kOuterDisplayHwcId = kInnerDisplayHwcId + 1;
 
@@ -309,13 +343,13 @@
 
     const auto& innerDisplay = mDisplay;
 
-    EXPECT_FALSE(innerDisplay->getDesiredActiveMode());
-    EXPECT_FALSE(outerDisplay->getDesiredActiveMode());
+    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
 
-    EXPECT_EQ(innerDisplay->getActiveMode().modePtr->getId(), kModeId60);
-    EXPECT_EQ(outerDisplay->getActiveMode().modePtr->getId(), kModeId120);
+    mFlinger.onActiveDisplayChanged(nullptr, *innerDisplay);
 
-    mFlinger.onActiveDisplayChanged(*innerDisplay);
+    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
@@ -327,12 +361,8 @@
                                                   mock::createDisplayModeSpecs(kModeId60.value(),
                                                                                false, 0.f, 120.f)));
 
-    // Transition on the inner display.
-    ASSERT_TRUE(innerDisplay->getDesiredActiveMode());
-    EXPECT_EQ(innerDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId90);
-
-    // No transition on the outer display.
-    EXPECT_FALSE(outerDisplay->getDesiredActiveMode());
+    EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
 
     const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
     EXPECT_CALL(*mComposer,
@@ -342,31 +372,18 @@
 
     mFlinger.commit();
 
-    // Transition on the inner display.
-    ASSERT_TRUE(innerDisplay->getDesiredActiveMode());
-    EXPECT_EQ(innerDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId90);
-
-    // No transition on the outer display.
-    EXPECT_FALSE(outerDisplay->getDesiredActiveMode());
+    EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
 
     mFlinger.commit();
 
-    // Transition on the inner display.
-    EXPECT_FALSE(innerDisplay->getDesiredActiveMode());
-    EXPECT_EQ(innerDisplay->getActiveMode().modePtr->getId(), kModeId90);
+    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
 
-    // No transition on the outer display.
-    EXPECT_FALSE(outerDisplay->getDesiredActiveMode());
-    EXPECT_EQ(outerDisplay->getActiveMode().modePtr->getId(), kModeId120);
+    mFlinger.onActiveDisplayChanged(innerDisplay.get(), *outerDisplay);
 
-    mFlinger.onActiveDisplayChanged(*outerDisplay);
-
-    // No transition on the inner display.
-    EXPECT_FALSE(innerDisplay->getDesiredActiveMode());
-
-    // Transition on the outer display.
-    ASSERT_TRUE(outerDisplay->getDesiredActiveMode());
-    EXPECT_EQ(outerDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId60);
+    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
 
     EXPECT_CALL(*mComposer,
                 setActiveConfigWithConstraints(kOuterDisplayHwcId,
@@ -375,22 +392,13 @@
 
     mFlinger.commit();
 
-    // No transition on the inner display.
-    EXPECT_FALSE(innerDisplay->getDesiredActiveMode());
-
-    // Transition on the outer display.
-    ASSERT_TRUE(outerDisplay->getDesiredActiveMode());
-    EXPECT_EQ(outerDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId60);
+    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
 
     mFlinger.commit();
 
-    // No transition on the inner display.
-    EXPECT_FALSE(innerDisplay->getDesiredActiveMode());
-    EXPECT_EQ(innerDisplay->getActiveMode().modePtr->getId(), kModeId90);
-
-    // Transition on the outer display.
-    EXPECT_FALSE(outerDisplay->getDesiredActiveMode());
-    EXPECT_EQ(outerDisplay->getActiveMode().modePtr->getId(), kModeId60);
+    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60));
 }
 
 } // namespace
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HdrOutputControlTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HdrOutputControlTest.cpp
new file mode 100644
index 0000000..a2c54ac
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HdrOutputControlTest.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include <gtest/gtest.h>
+#include <gui/AidlStatusUtil.h>
+#include <private/gui/ComposerService.h>
+#include <private/gui/ComposerServiceAIDL.h>
+
+#include "DisplayTransactionTestHelpers.h"
+
+namespace android {
+
+using aidl::android::hardware::graphics::common::HdrConversionCapability;
+using aidl::android::hardware::graphics::common::HdrConversionStrategy;
+using GuiHdrConversionStrategyTag = gui::HdrConversionStrategy::Tag;
+using gui::aidl_utils::statusTFromBinderStatus;
+
+TEST(HdrOutputControlTest, testGetHdrOutputConversionSupport) {
+    sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
+
+    bool hdrOutputConversionSupport;
+    binder::Status status = sf->getHdrOutputConversionSupport(&hdrOutputConversionSupport);
+
+    ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+}
+
+TEST(HdrOutputControlTest, testGetHdrConversionCapabilities) {
+    sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
+
+    bool hdrOutputConversionSupport;
+    binder::Status getSupportStatus =
+            sf->getHdrOutputConversionSupport(&hdrOutputConversionSupport);
+    ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(getSupportStatus));
+
+    std::vector<gui::HdrConversionCapability> capabilities;
+    binder::Status status = sf->getHdrConversionCapabilities(&capabilities);
+
+    if (hdrOutputConversionSupport) {
+        ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+    } else {
+        ASSERT_EQ(INVALID_OPERATION, statusTFromBinderStatus(status));
+    }
+}
+
+TEST(HdrOutputControlTest, testSetHdrConversionStrategy) {
+    sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
+
+    bool hdrOutputConversionSupport;
+    binder::Status getSupportStatus =
+            sf->getHdrOutputConversionSupport(&hdrOutputConversionSupport);
+    ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(getSupportStatus));
+
+    std::vector<HdrConversionStrategy> strategies =
+            {HdrConversionStrategy(std::in_place_index<static_cast<size_t>(
+                                           GuiHdrConversionStrategyTag::passthrough)>),
+             HdrConversionStrategy(std::in_place_index<static_cast<size_t>(
+                                           GuiHdrConversionStrategyTag::autoAllowedHdrTypes)>),
+             HdrConversionStrategy(std::in_place_index<static_cast<size_t>(
+                                           GuiHdrConversionStrategyTag::forceHdrConversion)>)};
+    int32_t outPreferredHdrOutputType = 0;
+
+    for (HdrConversionStrategy strategy : strategies) {
+        binder::Status status = sf->setHdrConversionStrategy(&strategy, &outPreferredHdrOutputType);
+
+        if (hdrOutputConversionSupport) {
+            ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+        } else {
+            ASSERT_EQ(INVALID_OPERATION, statusTFromBinderStatus(status));
+        }
+    }
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
similarity index 93%
rename from services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp
rename to services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
index 98644aa..fc5f2b0 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
@@ -22,9 +22,9 @@
 namespace android {
 namespace {
 
-class OnInitializeDisplaysTest : public DisplayTransactionTest {};
+class InitializeDisplaysTest : public DisplayTransactionTest {};
 
-TEST_F(OnInitializeDisplaysTest, onInitializeDisplaysSetsUpPrimaryDisplay) {
+TEST_F(InitializeDisplaysTest, commitsPrimaryDisplay) {
     using Case = SimplePrimaryDisplayCase;
 
     // --------------------------------------------------------------------
@@ -52,7 +52,7 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    mFlinger.onInitializeDisplays();
+    FTL_FAKE_GUARD(kMainThreadContext, mFlinger.initializeDisplays());
 
     // --------------------------------------------------------------------
     // Postconditions
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_MultiDisplayLeaderTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_MultiDisplayPacesetterTest.cpp
similarity index 63%
rename from services/surfaceflinger/tests/unittests/SurfaceFlinger_MultiDisplayLeaderTest.cpp
rename to services/surfaceflinger/tests/unittests/SurfaceFlinger_MultiDisplayPacesetterTest.cpp
index 9c58943..e38f56e 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_MultiDisplayLeaderTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_MultiDisplayPacesetterTest.cpp
@@ -25,12 +25,12 @@
 namespace android {
 namespace {
 
-struct MultiDisplayLeaderTest : DisplayTransactionTest {
+struct MultiDisplayPacesetterTest : DisplayTransactionTest {
     static constexpr bool kWithMockScheduler = false;
-    MultiDisplayLeaderTest() : DisplayTransactionTest(kWithMockScheduler) {}
+    MultiDisplayPacesetterTest() : DisplayTransactionTest(kWithMockScheduler) {}
 };
 
-TEST_F(MultiDisplayLeaderTest, foldable) {
+TEST_F(MultiDisplayPacesetterTest, foldable) {
     injectMockScheduler(InnerDisplayVariant::DISPLAY_ID::get());
 
     // Inject inner and outer displays with uninitialized power modes.
@@ -50,31 +50,31 @@
         outerDisplay = injector.inject();
     }
 
-    // When the device boots, the inner display should be the leader.
-    ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), innerDisplay->getPhysicalId());
+    // When the device boots, the inner display should be the pacesetter.
+    ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), innerDisplay->getPhysicalId());
 
     // ...and should still be after powering on.
     mFlinger.setPowerModeInternal(innerDisplay, PowerMode::ON);
-    ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), innerDisplay->getPhysicalId());
+    ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), innerDisplay->getPhysicalId());
 
-    // The outer display should become the leader after folding.
+    // The outer display should become the pacesetter after folding.
     mFlinger.setPowerModeInternal(innerDisplay, PowerMode::OFF);
     mFlinger.setPowerModeInternal(outerDisplay, PowerMode::ON);
-    ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), outerDisplay->getPhysicalId());
+    ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), outerDisplay->getPhysicalId());
 
-    // The inner display should become the leader after unfolding.
+    // The inner display should become the pacesetter after unfolding.
     mFlinger.setPowerModeInternal(outerDisplay, PowerMode::OFF);
     mFlinger.setPowerModeInternal(innerDisplay, PowerMode::ON);
-    ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), innerDisplay->getPhysicalId());
+    ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), innerDisplay->getPhysicalId());
 
-    // The inner display should stay the leader if both are powered on.
-    // TODO(b/256196556): The leader should depend on the displays' VSYNC phases.
+    // The inner display should stay the pacesetter if both are powered on.
+    // TODO(b/255635821): The pacesetter should depend on the displays' refresh rates.
     mFlinger.setPowerModeInternal(outerDisplay, PowerMode::ON);
-    ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), innerDisplay->getPhysicalId());
+    ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), innerDisplay->getPhysicalId());
 
-    // The outer display should become the leader if designated.
-    mFlinger.scheduler()->setLeaderDisplay(outerDisplay->getPhysicalId());
-    ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), outerDisplay->getPhysicalId());
+    // The outer display should become the pacesetter if designated.
+    mFlinger.scheduler()->setPacesetterDisplay(outerDisplay->getPhysicalId());
+    ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), outerDisplay->getPhysicalId());
 }
 
 } // namespace
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index ac04720..f1a5fc4 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -63,7 +63,7 @@
         return Scheduler::createConnection(std::move(eventThread));
     }
 
-    auto refreshRateSelector() { return leaderSelectorPtr(); }
+    auto refreshRateSelector() { return pacesetterSelectorPtr(); }
 
     const auto& refreshRateSelectors() const NO_THREAD_SAFETY_ANALYSIS {
         return mRefreshRateSelectors;
@@ -92,17 +92,16 @@
         Scheduler::unregisterDisplay(displayId);
     }
 
-    std::optional<PhysicalDisplayId> leaderDisplayId() const NO_THREAD_SAFETY_ANALYSIS {
-        return mLeaderDisplayId;
+    std::optional<PhysicalDisplayId> pacesetterDisplayId() const NO_THREAD_SAFETY_ANALYSIS {
+        return mPacesetterDisplayId;
     }
 
-    void setLeaderDisplay(PhysicalDisplayId displayId) {
+    void setPacesetterDisplay(PhysicalDisplayId displayId) {
         ftl::FakeGuard guard(kMainThreadContext);
-        Scheduler::setLeaderDisplay(displayId);
+        Scheduler::setPacesetterDisplay(displayId);
     }
 
     auto& mutableAppConnectionHandle() { return mAppConnectionHandle; }
-    auto& mutableVsyncModulator() { return *mVsyncModulator; }
     auto& mutableLayerHistory() { return mLayerHistory; }
 
     size_t layerHistorySize() NO_THREAD_SAFETY_ANALYSIS {
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 6334ec8..fc9e653 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -419,10 +419,7 @@
         return mFlinger->setDisplayStateLocked(s);
     }
 
-    // Allow reading display state without locking, as if called on the SF main thread.
-    auto onInitializeDisplays() NO_THREAD_SAFETY_ANALYSIS {
-        return mFlinger->onInitializeDisplays();
-    }
+    void initializeDisplays() FTL_FAKE_GUARD(kMainThreadContext) { mFlinger->initializeDisplays(); }
 
     auto notifyPowerBoost(int32_t boostId) { return mFlinger->notifyPowerBoost(boostId); }
 
@@ -505,10 +502,11 @@
         return mFlinger->setDesiredDisplayModeSpecs(displayToken, specs);
     }
 
-    void onActiveDisplayChanged(const DisplayDevice& activeDisplay) {
+    void onActiveDisplayChanged(const DisplayDevice* inactiveDisplayPtr,
+                                const DisplayDevice& activeDisplay) {
         Mutex::Autolock lock(mFlinger->mStateLock);
         ftl::FakeGuard guard(kMainThreadContext);
-        mFlinger->onActiveDisplayChangedLocked(nullptr, activeDisplay);
+        mFlinger->onActiveDisplayChangedLocked(inactiveDisplayPtr, activeDisplay);
     }
 
     auto createLayer(LayerCreationArgs& args, const sp<IBinder>& parentHandle,
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index c78148f..d4e2357 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -199,7 +199,7 @@
 
     void modulateVsync() {
         static_cast<void>(
-                mFlinger.mutableScheduler().mutableVsyncModulator().onRefreshRateChangeInitiated());
+                mFlinger.mutableScheduler().vsyncModulator().onRefreshRateChangeInitiated());
     }
 
     bool mHasListenerCallbacks = false;
diff --git a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
index b6427c0..3dea189 100644
--- a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
@@ -19,6 +19,7 @@
 #include <limits> // std::numeric_limits
 
 #include <gui/SurfaceComposerClient.h>
+#include "LayerProtoHelper.h"
 
 #include "Tracing/TransactionProtoParser.h"
 
@@ -27,7 +28,6 @@
 namespace android {
 
 TEST(TransactionProtoParserTest, parse) {
-    const sp<IBinder> layerHandle = sp<BBinder>::make();
     const sp<IBinder> displayHandle = sp<BBinder>::make();
     TransactionState t1;
     t1.originPid = 1;
@@ -37,7 +37,6 @@
     t1.postTime = 5;
 
     layer_state_t layer;
-    layer.layerId = 6;
     layer.what = std::numeric_limits<uint64_t>::max();
     layer.what &= ~static_cast<uint64_t>(layer_state_t::eBufferChanged);
     layer.x = 7;
@@ -48,10 +47,9 @@
     for (uint32_t i = 0; i < layerCount; i++) {
         ResolvedComposerState s;
         if (i == 1) {
-            layer.parentSurfaceControlForChild =
-                    sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(), layerHandle, 42,
-                                             "#42");
+            s.parentId = 42;
         }
+        s.layerId = 6 + i;
         s.state = layer;
         t1.states.emplace_back(s);
     }
@@ -72,18 +70,10 @@
 
     class TestMapper : public TransactionProtoParser::FlingerDataMapper {
     public:
-        sp<IBinder> layerHandle;
         sp<IBinder> displayHandle;
 
-        TestMapper(sp<IBinder> layerHandle, sp<IBinder> displayHandle)
-              : layerHandle(layerHandle), displayHandle(displayHandle) {}
+        TestMapper(sp<IBinder> displayHandle) : displayHandle(displayHandle) {}
 
-        sp<IBinder> getLayerHandle(int32_t id) const override {
-            return (id == 42) ? layerHandle : nullptr;
-        }
-        int64_t getLayerId(const sp<IBinder>& handle) const override {
-            return (handle == layerHandle) ? 42 : -1;
-        }
         sp<IBinder> getDisplayHandle(int32_t id) const {
             return (id == 43) ? displayHandle : nullptr;
         }
@@ -92,7 +82,7 @@
         }
     };
 
-    TransactionProtoParser parser(std::make_unique<TestMapper>(layerHandle, displayHandle));
+    TransactionProtoParser parser(std::make_unique<TestMapper>(displayHandle));
 
     proto::TransactionState proto = parser.toProto(t1);
     TransactionState t2 = parser.fromProto(proto);
@@ -105,8 +95,8 @@
     ASSERT_EQ(t1.states.size(), t2.states.size());
     ASSERT_EQ(t1.states[0].state.x, t2.states[0].state.x);
     ASSERT_EQ(t1.states[0].state.matrix.dsdx, t2.states[0].state.matrix.dsdx);
-    ASSERT_EQ(t1.states[1].state.parentSurfaceControlForChild->getHandle(),
-              t2.states[1].state.parentSurfaceControlForChild->getHandle());
+    ASSERT_EQ(t1.states[1].layerId, t2.states[1].layerId);
+    ASSERT_EQ(t1.states[1].parentId, t2.states[1].parentId);
 
     ASSERT_EQ(t1.displays.size(), t2.displays.size());
     ASSERT_EQ(t1.displays[1].width, t2.displays[1].width);
diff --git a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
index 482c3a8..82aac7e 100644
--- a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
@@ -18,7 +18,13 @@
 #include <gtest/gtest.h>
 
 #include <gui/SurfaceComposerClient.h>
+#include <cstdint>
+#include "Client.h"
 
+#include <layerproto/LayerProtoHeader.h>
+#include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/Update.h"
+#include "Tracing/LayerTracing.h"
 #include "Tracing/RingBuffer.h"
 #include "Tracing/TransactionTracing.h"
 
@@ -42,14 +48,15 @@
     }
 
     void queueAndCommitTransaction(int64_t vsyncId) {
+        frontend::Update update;
         TransactionState transaction;
         transaction.id = static_cast<uint64_t>(vsyncId * 3);
         transaction.originUid = 1;
         transaction.originPid = 2;
         mTracing.addQueuedTransaction(transaction);
         std::vector<TransactionState> transactions;
-        transactions.emplace_back(transaction);
-        mTracing.addCommittedTransactions(transactions, vsyncId);
+        update.transactions.emplace_back(transaction);
+        mTracing.addCommittedTransactions(vsyncId, 0, update, {}, false);
         flush(vsyncId);
     }
 
@@ -57,13 +64,25 @@
                      const std::vector<TransactionState>& expectedTransactions,
                      int64_t expectedVsyncId) {
         EXPECT_EQ(actualProto.vsync_id(), expectedVsyncId);
-        EXPECT_EQ(actualProto.transactions().size(),
+        ASSERT_EQ(actualProto.transactions().size(),
                   static_cast<int32_t>(expectedTransactions.size()));
         for (uint32_t i = 0; i < expectedTransactions.size(); i++) {
             EXPECT_EQ(actualProto.transactions(static_cast<int32_t>(i)).pid(),
                       expectedTransactions[i].originPid);
         }
     }
+
+    LayerCreationArgs getLayerCreationArgs(uint32_t layerId, uint32_t parentId,
+                                           uint32_t layerIdToMirror, uint32_t flags,
+                                           bool addToRoot) {
+        LayerCreationArgs args;
+        args.sequence = layerId;
+        args.parentId = parentId;
+        args.layerIdToMirror = layerIdToMirror;
+        args.flags = flags;
+        args.addToRoot = addToRoot;
+        return args;
+    }
 };
 
 TEST_F(TransactionTracingTest, addTransactions) {
@@ -79,57 +98,59 @@
 
     // Split incoming transactions into two and commit them in reverse order to test out of order
     // commits.
-    std::vector<TransactionState> firstTransactionSet =
-            std::vector<TransactionState>(transactions.begin() + 50, transactions.end());
     int64_t firstTransactionSetVsyncId = 42;
-    mTracing.addCommittedTransactions(firstTransactionSet, firstTransactionSetVsyncId);
+    frontend::Update firstUpdate;
+    firstUpdate.transactions =
+            std::vector<TransactionState>(transactions.begin() + 50, transactions.end());
+    mTracing.addCommittedTransactions(firstTransactionSetVsyncId, 0, firstUpdate, {}, false);
 
     int64_t secondTransactionSetVsyncId = 43;
-    std::vector<TransactionState> secondTransactionSet =
+    frontend::Update secondUpdate;
+    secondUpdate.transactions =
             std::vector<TransactionState>(transactions.begin(), transactions.begin() + 50);
-    mTracing.addCommittedTransactions(secondTransactionSet, secondTransactionSetVsyncId);
+    mTracing.addCommittedTransactions(secondTransactionSetVsyncId, 0, secondUpdate, {}, false);
     flush(secondTransactionSetVsyncId);
 
     proto::TransactionTraceFile proto = writeToProto();
-    EXPECT_EQ(proto.entry().size(), 2);
-    verifyEntry(proto.entry(0), firstTransactionSet, firstTransactionSetVsyncId);
-    verifyEntry(proto.entry(1), secondTransactionSet, secondTransactionSetVsyncId);
+    ASSERT_EQ(proto.entry().size(), 2);
+    verifyEntry(proto.entry(0), firstUpdate.transactions, firstTransactionSetVsyncId);
+    verifyEntry(proto.entry(1), secondUpdate.transactions, secondTransactionSetVsyncId);
 }
 
 class TransactionTracingLayerHandlingTest : public TransactionTracingTest {
 protected:
     void SetUp() override {
-        // add layers
         mTracing.setBufferSize(SMALL_BUFFER_SIZE);
-        const sp<IBinder> fakeLayerHandle = sp<BBinder>::make();
-        mTracing.onLayerAdded(fakeLayerHandle->localBinder(), mParentLayerId, "parent",
-                              123 /* flags */, -1 /* parentId */);
-        const sp<IBinder> fakeChildLayerHandle = sp<BBinder>::make();
-        mTracing.onLayerAdded(fakeChildLayerHandle->localBinder(), mChildLayerId, "child",
-                              456 /* flags */, mParentLayerId);
 
-        // add some layer transaction
+        // add layers and add some layer transaction
         {
+            frontend::Update update;
+            update.layerCreationArgs.emplace_back(std::move(
+                    getLayerCreationArgs(mParentLayerId, /*parentId=*/UNASSIGNED_LAYER_ID,
+                                         /*layerIdToMirror=*/UNASSIGNED_LAYER_ID, /*flags=*/123,
+                                         /*addToRoot=*/true)));
+            update.layerCreationArgs.emplace_back(std::move(
+                    getLayerCreationArgs(mChildLayerId, mParentLayerId,
+                                         /*layerIdToMirror=*/UNASSIGNED_LAYER_ID, /*flags=*/456,
+                                         /*addToRoot=*/true)));
             TransactionState transaction;
             transaction.id = 50;
             ResolvedComposerState layerState;
-            layerState.state.surface = fakeLayerHandle;
+            layerState.layerId = mParentLayerId;
             layerState.state.what = layer_state_t::eLayerChanged;
             layerState.state.z = 42;
             transaction.states.emplace_back(layerState);
             ResolvedComposerState childState;
-            childState.state.surface = fakeChildLayerHandle;
+            childState.layerId = mChildLayerId;
             childState.state.what = layer_state_t::eLayerChanged;
             childState.state.z = 43;
             transaction.states.emplace_back(childState);
             mTracing.addQueuedTransaction(transaction);
 
-            std::vector<TransactionState> transactions;
-            transactions.emplace_back(transaction);
+            update.transactions.emplace_back(transaction);
             VSYNC_ID_FIRST_LAYER_CHANGE = ++mVsyncId;
-            mTracing.onLayerAddedToDrawingState(mParentLayerId, VSYNC_ID_FIRST_LAYER_CHANGE);
-            mTracing.onLayerAddedToDrawingState(mChildLayerId, VSYNC_ID_FIRST_LAYER_CHANGE);
-            mTracing.addCommittedTransactions(transactions, VSYNC_ID_FIRST_LAYER_CHANGE);
+            mTracing.addCommittedTransactions(VSYNC_ID_FIRST_LAYER_CHANGE, 0, update, {}, false);
+
             flush(VSYNC_ID_FIRST_LAYER_CHANGE);
         }
 
@@ -139,17 +160,17 @@
             TransactionState transaction;
             transaction.id = 51;
             ResolvedComposerState layerState;
-            layerState.state.surface = fakeLayerHandle;
+            layerState.layerId = mParentLayerId;
             layerState.state.what = layer_state_t::eLayerChanged | layer_state_t::ePositionChanged;
             layerState.state.z = 41;
             layerState.state.x = 22;
             transaction.states.emplace_back(layerState);
             mTracing.addQueuedTransaction(transaction);
 
-            std::vector<TransactionState> transactions;
-            transactions.emplace_back(transaction);
+            frontend::Update update;
+            update.transactions.emplace_back(transaction);
             VSYNC_ID_SECOND_LAYER_CHANGE = ++mVsyncId;
-            mTracing.addCommittedTransactions(transactions, VSYNC_ID_SECOND_LAYER_CHANGE);
+            mTracing.addCommittedTransactions(VSYNC_ID_SECOND_LAYER_CHANGE, 0, update, {}, false);
             flush(VSYNC_ID_SECOND_LAYER_CHANGE);
         }
 
@@ -163,8 +184,8 @@
         queueAndCommitTransaction(++mVsyncId);
     }
 
-    int mParentLayerId = 1;
-    int mChildLayerId = 2;
+    uint32_t mParentLayerId = 1;
+    uint32_t mChildLayerId = 2;
     int64_t mVsyncId = 0;
     int64_t VSYNC_ID_FIRST_LAYER_CHANGE;
     int64_t VSYNC_ID_SECOND_LAYER_CHANGE;
@@ -232,42 +253,42 @@
 class TransactionTracingMirrorLayerTest : public TransactionTracingTest {
 protected:
     void SetUp() override {
-        // add layers
         mTracing.setBufferSize(SMALL_BUFFER_SIZE);
-        const sp<IBinder> fakeLayerHandle = sp<BBinder>::make();
-        mTracing.onLayerAdded(fakeLayerHandle->localBinder(), mLayerId, "Test Layer",
-                              123 /* flags */, -1 /* parentId */);
-        const sp<IBinder> fakeMirrorLayerHandle = sp<BBinder>::make();
-        mTracing.onMirrorLayerAdded(fakeMirrorLayerHandle->localBinder(), mMirrorLayerId, "Mirror",
-                                    mLayerId);
-        mTracing.onLayerAddedToDrawingState(mLayerId, mVsyncId);
-        mTracing.onLayerAddedToDrawingState(mMirrorLayerId, mVsyncId);
 
-        // add some layer transaction
+        // add layers and some layer transaction
         {
+            frontend::Update update;
+            update.layerCreationArgs.emplace_back(
+                    getLayerCreationArgs(mLayerId, /*parentId=*/UNASSIGNED_LAYER_ID,
+                                         /*layerIdToMirror=*/UNASSIGNED_LAYER_ID, /*flags=*/123,
+                                         /*addToRoot=*/true));
+            update.layerCreationArgs.emplace_back(
+                    getLayerCreationArgs(mMirrorLayerId, UNASSIGNED_LAYER_ID,
+                                         /*layerIdToMirror=*/mLayerId, /*flags=*/0,
+                                         /*addToRoot=*/false));
+
             TransactionState transaction;
             transaction.id = 50;
             ResolvedComposerState layerState;
-            layerState.state.surface = fakeLayerHandle;
+            layerState.layerId = mLayerId;
             layerState.state.what = layer_state_t::eLayerChanged;
             layerState.state.z = 42;
             transaction.states.emplace_back(layerState);
             ResolvedComposerState mirrorState;
-            mirrorState.state.surface = fakeMirrorLayerHandle;
+            mirrorState.layerId = mMirrorLayerId;
             mirrorState.state.what = layer_state_t::eLayerChanged;
             mirrorState.state.z = 43;
             transaction.states.emplace_back(mirrorState);
             mTracing.addQueuedTransaction(transaction);
 
-            std::vector<TransactionState> transactions;
-            transactions.emplace_back(transaction);
-            mTracing.addCommittedTransactions(transactions, mVsyncId);
+            update.transactions.emplace_back(transaction);
+            mTracing.addCommittedTransactions(mVsyncId, 0, update, {}, false);
             flush(mVsyncId);
         }
     }
 
-    int mLayerId = 5;
-    int mMirrorLayerId = 55;
+    uint32_t mLayerId = 5;
+    uint32_t mMirrorLayerId = 55;
     int64_t mVsyncId = 0;
     int64_t VSYNC_ID_FIRST_LAYER_CHANGE;
     int64_t VSYNC_ID_SECOND_LAYER_CHANGE;
@@ -286,4 +307,24 @@
     EXPECT_EQ(proto.entry(0).transactions(0).layer_changes().size(), 2);
     EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(1).z(), 43);
 }
+
+// Verify we can write the layers traces by entry to reduce mem pressure
+// on the system when generating large traces.
+TEST(LayerTraceTest, canStreamLayersTrace) {
+    LayersTraceFileProto inProto = LayerTracing::createTraceFileProto();
+    inProto.add_entry();
+    inProto.add_entry();
+
+    std::string output;
+    inProto.SerializeToString(&output);
+    LayersTraceFileProto inProto2 = LayerTracing::createTraceFileProto();
+    inProto2.add_entry();
+    std::string output2;
+    inProto2.SerializeToString(&output2);
+
+    LayersTraceFileProto outProto;
+    outProto.ParseFromString(output + output2);
+    // magic?
+    EXPECT_EQ(outProto.entry().size(), 3);
+}
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp b/services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp
index adf0804..4010fa6 100644
--- a/services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp
@@ -86,6 +86,12 @@
     mVsyncSchedule->disableHardwareVsync(mCallback, false /* disallow */);
 }
 
+TEST_F(VsyncScheduleTest, DisableDoesNothingWhenDisallowed2) {
+    EXPECT_CALL(mCallback, setVsyncEnabled(_, _)).Times(0);
+
+    mVsyncSchedule->disableHardwareVsync(mCallback, true /* disallow */);
+}
+
 TEST_F(VsyncScheduleTest, MakeAllowed) {
     ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
 }
@@ -97,6 +103,13 @@
     mVsyncSchedule->disableHardwareVsync(mCallback, false /* disallow */);
 }
 
+TEST_F(VsyncScheduleTest, DisableDoesNothingWhenDisabled2) {
+    ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+    EXPECT_CALL(mCallback, setVsyncEnabled(_, _)).Times(0);
+
+    mVsyncSchedule->disableHardwareVsync(mCallback, true /* disallow */);
+}
+
 TEST_F(VsyncScheduleTest, EnableWorksWhenDisabled) {
     ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
     EXPECT_CALL(mCallback, setVsyncEnabled(DEFAULT_DISPLAY_ID, true));
@@ -129,6 +142,16 @@
     mVsyncSchedule->disableHardwareVsync(mCallback, false /* disallow */);
 }
 
+TEST_F(VsyncScheduleTest, EnableDisable2) {
+    ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+    EXPECT_CALL(mCallback, setVsyncEnabled(DEFAULT_DISPLAY_ID, true));
+
+    mVsyncSchedule->enableHardwareVsync(mCallback);
+
+    EXPECT_CALL(mCallback, setVsyncEnabled(DEFAULT_DISPLAY_ID, false));
+    mVsyncSchedule->disableHardwareVsync(mCallback, true /* disallow */);
+}
+
 TEST_F(VsyncScheduleTest, StartPeriodTransition) {
     // Note: startPeriodTransition is only called when hardware vsyncs are
     // allowed.
@@ -225,5 +248,23 @@
     ASSERT_FALSE(mVsyncSchedule->getPendingHardwareVsyncState());
 }
 
+TEST_F(VsyncScheduleTest, DisableDoesNotMakeAllowed) {
+    ASSERT_FALSE(mVsyncSchedule->isHardwareVsyncAllowed(false /* makeAllowed */));
+    mVsyncSchedule->disableHardwareVsync(mCallback, false /* disallow */);
+    ASSERT_FALSE(mVsyncSchedule->isHardwareVsyncAllowed(false /* makeAllowed */));
+}
+
+TEST_F(VsyncScheduleTest, DisallowMakesNotAllowed) {
+    ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+    mVsyncSchedule->disableHardwareVsync(mCallback, true /* disallow */);
+    ASSERT_FALSE(mVsyncSchedule->isHardwareVsyncAllowed(false /* makeAllowed */));
+}
+
+TEST_F(VsyncScheduleTest, StillAllowedAfterDisable) {
+    ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+    mVsyncSchedule->disableHardwareVsync(mCallback, false /* disallow */);
+    ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(false /* makeAllowed */));
+}
+
 } // namespace
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index f28b8d8..5dc3490 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -175,6 +175,7 @@
                  Error(aidl::android::hardware::graphics::composer3::OverlayProperties*));
     MOCK_METHOD1(onHotplugConnect, void(Display));
     MOCK_METHOD1(onHotplugDisconnect, void(Display));
+    MOCK_METHOD(Error, setRefreshRateChangedCallbackDebugEnabled, (Display, bool));
 };
 
 } // namespace Hwc2::mock