Merge "Revert^2 "Use "SessionHint" enum in ndk API"" into udc-dev
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index c71c4a0..2ce3fb0 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -7,6 +7,7 @@
 rustfmt = --config-path=rustfmt.toml
 # Only turn on clang-format check for the following subfolders.
 clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
+               cmds/dumpstate/
                cmds/idlcli/
                cmds/installd/
                cmds/servicemanager/
@@ -14,6 +15,7 @@
                include/powermanager/
                libs/binder/fuzzer/
                libs/binder/
+               libs/binderdebug/
                libs/binderthreadstate/
                libs/graphicsenv/
                libs/gui/
@@ -40,3 +42,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/Android.bp b/cmds/atrace/Android.bp
index aa0ef25..1c4e63e 100644
--- a/cmds/atrace/Android.bp
+++ b/cmds/atrace/Android.bp
@@ -38,6 +38,7 @@
     ],
 
     init_rc: ["atrace.rc"],
+    required: ["ftrace_synthetic_events.conf"],
 
     product_variables: {
         debuggable: {
@@ -45,3 +46,8 @@
         },
     },
 }
+
+prebuilt_etc {
+    name: "ftrace_synthetic_events.conf",
+    src: "ftrace_synthetic_events.conf",
+}
diff --git a/cmds/atrace/atrace.rc b/cmds/atrace/atrace.rc
index 95f5c03..f1d8c72 100644
--- a/cmds/atrace/atrace.rc
+++ b/cmds/atrace/atrace.rc
@@ -291,12 +291,10 @@
 # Setup synthetic events
     chmod 0666 /sys/kernel/tracing/synthetic_events
     chmod 0666 /sys/kernel/debug/tracing/synthetic_events
+    copy /system/etc/ftrace_synthetic_events.conf /sys/kernel/tracing/synthetic_events
+    copy /system/etc/ftrace_synthetic_events.conf /sys/kernel/debug/tracing/synthetic_events
 
-    # rss_stat_throttled
-    write /sys/kernel/tracing/synthetic_events "rss_stat_throttled unsigned int mm_id; unsigned int curr; int member; long size"
-    write /sys/kernel/debug/tracing/synthetic_events "rss_stat_throttled unsigned int mm_id; unsigned int curr; int member; long size"
-
-    # allow creating event triggers
+    # allow creating rss_stat event triggers
     chmod 0666 /sys/kernel/tracing/events/kmem/rss_stat/trigger
     chmod 0666 /sys/kernel/debug/tracing/events/kmem/rss_stat/trigger
 
@@ -304,6 +302,14 @@
     chmod 0666 /sys/kernel/tracing/events/synthetic/rss_stat_throttled/enable
     chmod 0666 /sys/kernel/debug/tracing/events/synthetic/rss_stat_throttled/enable
 
+    # allow creating suspend_resume triggers
+    chmod 0666 /sys/kernel/tracing/events/power/suspend_resume/trigger
+    chmod 0666 /sys/kernel/debug/tracing/events/power/suspend_resume/trigger
+
+    # allow enabling suspend_resume_minimal
+    chmod 0666 /sys/kernel/tracing/events/synthetic/suspend_resume_minimal/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/synthetic/suspend_resume_minimal/enable
+
 on late-init && property:ro.boot.fastboot.boottrace=enabled
     setprop debug.atrace.tags.enableflags 802922
     setprop persist.traced.enable 0
@@ -501,12 +507,40 @@
     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
 
 # Run atrace with the categories written in a file
 service boottrace /system/bin/atrace --async_start -f /data/misc/boottrace/categories
+    user root
     disabled
     oneshot
 
diff --git a/cmds/atrace/ftrace_synthetic_events.conf b/cmds/atrace/ftrace_synthetic_events.conf
new file mode 100644
index 0000000..e2257fe
--- /dev/null
+++ b/cmds/atrace/ftrace_synthetic_events.conf
@@ -0,0 +1,2 @@
+rss_stat_throttled unsigned int mm_id; unsigned int curr; int member; long size
+suspend_resume_minimal bool start
diff --git a/cmds/cmd/fuzzer/Android.bp b/cmds/cmd/fuzzer/Android.bp
index a65f6de..faf461a 100644
--- a/cmds/cmd/fuzzer/Android.bp
+++ b/cmds/cmd/fuzzer/Android.bp
@@ -42,5 +42,13 @@
             "android-media-fuzzing-reports@google.com",
         ],
         componentid: 155276,
+        hotlists: [
+            "4593311",
+        ],
+        description: "The fuzzer targets the APIs of libcmd",
+        vector: "local_no_privileges_required",
+        service_privilege: "constrained",
+        users: "multi_user",
+        fuzzed_code_usage: "shipped",
     },
 }
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 23cdd10..5dbf7ac 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -816,6 +816,8 @@
     printf("Kernel: ");
     DumpFileToFd(STDOUT_FILENO, "", "/proc/version");
     printf("Command line: %s\n", strtok(cmdline_buf, "\n"));
+    printf("Bootconfig: ");
+    DumpFileToFd(STDOUT_FILENO, "", "/proc/bootconfig");
     printf("Uptime: ");
     RunCommandToFd(STDOUT_FILENO, "", {"uptime", "-p"},
                    CommandOptions::WithTimeout(1).Always().Build());
@@ -1254,8 +1256,10 @@
              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 |
+                                                       Dumpsys::TYPE_CLIENTS | Dumpsys::TYPE_THREAD,
+                                                       service, args);
+             if (status == OK) {
                 dumpsys.writeDumpHeader(STDOUT_FILENO, service, priority);
                 std::chrono::duration<double> elapsed_seconds;
                 if (priority == IServiceManager::DUMP_FLAG_PRIORITY_HIGH &&
@@ -1272,6 +1276,9 @@
                 dumpsys.writeDumpFooter(STDOUT_FILENO, service, elapsed_seconds);
                 bool dump_complete = (status == OK);
                 dumpsys.stopDumpThread(dump_complete);
+            } else {
+                MYLOGE("Failed to start dump thread for service: %s, status: %d",
+                       String8(service).c_str(), status);
             }
         }
 
@@ -1902,6 +1909,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);
@@ -2062,6 +2072,8 @@
                SEC_TO_MSEC(10));
     RunDumpsys("DUMPSYS", {"telephony.registry"}, CommandOptions::WithTimeout(90).Build(),
                SEC_TO_MSEC(10));
+    RunDumpsys("DUMPSYS", {"isub"}, CommandOptions::WithTimeout(90).Build(),
+               SEC_TO_MSEC(10));
     RunDumpsys("DUMPSYS", {"telecom"}, CommandOptions::WithTimeout(90).Build(),
                SEC_TO_MSEC(10));
     if (include_sensitive_info) {
@@ -2194,6 +2206,16 @@
             continue;
         }
 
+        // Skip cached processes.
+        if (IsCached(pid)) {
+            // For consistency, the header and footer to this message match those
+            // dumped by debuggerd in the success case.
+            dprintf(fd, "\n---- pid %d at [unknown] ----\n", pid);
+            dprintf(fd, "Dump skipped for cached process.\n");
+            dprintf(fd, "---- end %d ----", pid);
+            continue;
+        }
+
         const std::string link_name = android::base::StringPrintf("/proc/%d/exe", pid);
         std::string exe;
         if (!android::base::Readlink(link_name, &exe)) {
@@ -2224,8 +2246,7 @@
 
         const uint64_t start = Nanotime();
         const int ret = dump_backtrace_to_file_timeout(
-            pid, is_java_process ? kDebuggerdJavaBacktrace : kDebuggerdNativeBacktrace,
-            is_java_process ? 5 : 20, fd);
+            pid, is_java_process ? kDebuggerdJavaBacktrace : kDebuggerdNativeBacktrace, 3, fd);
 
         if (ret == -1) {
             // For consistency, the header and footer to this message match those
@@ -2804,6 +2825,7 @@
             options->do_screenshot = false;
             break;
         case Dumpstate::BugreportMode::BUGREPORT_WEAR:
+            options->do_vibrate = false;
             options->do_progress_updates = true;
             options->do_screenshot = is_screenshot_requested;
             break;
@@ -3340,24 +3362,25 @@
         return;
     }
 
-    // Include the proto logging from WMShell.
-    RunCommand(
-        // Empty name because it's not intended to be classified as a bugreport section.
-        // Actual logging files can be found as "/data/misc/wmtrace/shell_log.winscope"
-        // in the bugreport.
-        "", {"dumpsys", "activity", "service", "SystemUIService",
-             "WMShell", "protolog", "save-for-bugreport"},
-        CommandOptions::WithTimeout(10).Always().DropRoot().RedirectStderr().Build());
+    const std::vector<std::vector<std::string>> dumpTracesForBugReportCommands = {
+        {"dumpsys", "activity", "service", "SystemUIService", "WMShell", "protolog",
+         "save-for-bugreport"},
+        {"dumpsys", "activity", "service", "SystemUIService", "WMShell", "transitions", "tracing",
+         "save-for-bugreport"},
+        {"cmd", "input_method", "tracing", "save-for-bugreport"},
+        {"cmd", "window", "tracing", "save-for-bugreport"},
+        {"cmd", "window", "shell", "tracing", "save-for-bugreport"},
+    };
 
-    // Currently WindowManagerService and InputMethodManagerSerivice support WinScope protocol.
-    for (const auto& service : {"input_method", "window"}) {
+    for (const auto& command : dumpTracesForBugReportCommands) {
         RunCommand(
             // Empty name because it's not intended to be classified as a bugreport section.
             // Actual tracing files can be found in "/data/misc/wmtrace/" in the bugreport.
-            "", {"cmd", service, "tracing", "save-for-bugreport"},
+            "", command,
             CommandOptions::WithTimeout(10).Always().DropRoot().RedirectStderr().Build());
     }
 
+    // This command needs to be run as root
     static const auto SURFACEFLINGER_COMMAND_SAVE_ALL_TRACES = std::vector<std::string> {
         "service", "call", "SurfaceFlinger", "1042"
     };
diff --git a/cmds/dumpstate/dumpstate.rc b/cmds/dumpstate/dumpstate.rc
index a80da4e..d0030dd 100644
--- a/cmds/dumpstate/dumpstate.rc
+++ b/cmds/dumpstate/dumpstate.rc
@@ -8,6 +8,7 @@
     socket dumpstate stream 0660 shell log
     disabled
     oneshot
+    user root
 
 # dumpstatez generates a zipped bugreport but also uses a socket to print the file location once
 # it is finished.
@@ -16,9 +17,11 @@
     class main
     disabled
     oneshot
+    user root
 
 # bugreportd starts dumpstate binder service and makes it wait for a listener to connect.
 service bugreportd /system/bin/dumpstate -w
     class main
     disabled
     oneshot
+    user root
diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp
index aa5219b..a417837 100644
--- a/cmds/dumpstate/tests/dumpstate_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_test.cpp
@@ -286,8 +286,8 @@
 
 
     // Other options retain default values
-    EXPECT_TRUE(options_.do_vibrate);
     EXPECT_FALSE(options_.progress_updates_to_socket);
+    EXPECT_FALSE(options_.do_vibrate);
     EXPECT_FALSE(options_.show_header_only);
     EXPECT_FALSE(options_.is_remote_mode);
     EXPECT_FALSE(options_.stream_to_socket);
@@ -997,17 +997,22 @@
     EXPECT_FALSE(ds.dump_pool_);
 }
 
-TEST_F(DumpstateBaseTest, PreDumpUiData) {
-    // SurfaceFlinger's transactions trace is always enabled, i.e. it is always pre-dumped
-    static const auto kTransactionsTrace =
-            std::filesystem::path {"/data/misc/wmtrace/transactions_trace.winscope"};
+TEST_F(DumpstateTest, PreDumpUiData) {
+    // These traces are always enabled, i.e. they are always pre-dumped
+    const std::vector<std::filesystem::path> uiTraces = {
+        std::filesystem::path{"/data/misc/wmtrace/transactions_trace.winscope"},
+        std::filesystem::path{"/data/misc/wmtrace/wm_transition_trace.winscope"},
+        std::filesystem::path{"/data/misc/wmtrace/shell_transition_trace.winscope"},
+    };
 
-    std::system(("rm " + kTransactionsTrace.string()).c_str());
-    EXPECT_FALSE(std::filesystem::exists(kTransactionsTrace));
+    for (const auto traceFile : uiTraces) {
+        std::system(("rm -f " + traceFile.string()).c_str());
+        EXPECT_FALSE(std::filesystem::exists(traceFile)) << traceFile << " was not deleted.";
 
-    Dumpstate& ds_ = Dumpstate::GetInstance();
-    ds_.PreDumpUiData();
-    EXPECT_TRUE(std::filesystem::exists(kTransactionsTrace));
+        Dumpstate& ds_ = Dumpstate::GetInstance();
+        ds_.PreDumpUiData();
+        EXPECT_TRUE(std::filesystem::exists(traceFile)) << traceFile << " was not created.";
+    }
 }
 
 class ZippedBugReportStreamTest : public DumpstateBaseTest {
diff --git a/cmds/installd/dexopt.cpp b/cmds/installd/dexopt.cpp
index ce3d669..1693ed5 100644
--- a/cmds/installd/dexopt.cpp
+++ b/cmds/installd/dexopt.cpp
@@ -1917,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;
     }
 
@@ -1941,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;
@@ -1963,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.rc b/cmds/installd/installd.rc
index 5b08c77..525f0c8 100644
--- a/cmds/installd/installd.rc
+++ b/cmds/installd/installd.rc
@@ -1,6 +1,7 @@
 
 service installd /system/bin/installd
     class main
+    user root
     capabilities CHOWN DAC_OVERRIDE DAC_READ_SEARCH FOWNER FSETID KILL SETGID SETUID SYS_ADMIN
 
 on early-boot
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..c4071c6 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_;
@@ -199,6 +203,10 @@
     std::string secondary_dex_de_;
 
     virtual void SetUp() {
+        if (base::GetBoolProperty("dalvik.vm.useartservice", false)) {
+            GTEST_SKIP() << "Skipping legacy dexopt tests when ART Service is enabled";
+        }
+
         setenv("ANDROID_LOG_TAGS", "*:v", 1);
         android::base::InitLogging(nullptr);
         // Initialize the globals holding the file system main paths (/data/, /system/ etc..).
@@ -219,6 +227,10 @@
     }
 
     virtual void TearDown() {
+        if (base::GetBoolProperty("dalvik.vm.useartservice", false)) {
+            GTEST_SKIP();
+        }
+
         if (!kDebug) {
             service_->controlDexOptBlocking(false);
             service_->destroyAppData(
@@ -248,26 +260,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 +308,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 +640,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 +689,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 +819,7 @@
                         kTestAppGid,
                         DEX2OAT_FROM_SCRATCH,
                         /*binder_result=*/nullptr,
-                        empty_dm_file_.c_str());
+                        dm_file_.c_str());
 }
 
 TEST_F(DexoptTest, DexoptPrimaryProfilePublic) {
@@ -799,7 +831,7 @@
                         kTestAppGid,
                         DEX2OAT_FROM_SCRATCH,
                         /*binder_result=*/nullptr,
-                        empty_dm_file_.c_str());
+                        dm_file_.c_str());
 }
 
 TEST_F(DexoptTest, DexoptPrimaryBackgroundOk) {
@@ -811,7 +843,7 @@
                         kTestAppGid,
                         DEX2OAT_FROM_SCRATCH,
                         /*binder_result=*/nullptr,
-                        empty_dm_file_.c_str());
+                        dm_file_.c_str());
 }
 
 TEST_F(DexoptTest, DexoptBlockPrimary) {
@@ -874,7 +906,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 +925,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 +958,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,12 +968,16 @@
                         kTestAppGid,
                         DEX2OAT_FROM_SCRATCH,
                         /*binder_result=*/nullptr,
-                        empty_dm_file_.c_str());
+                        dm_file_.c_str());
 }
 
 class PrimaryDexReCompilationTest : public DexoptTest {
   public:
     virtual void SetUp() {
+        if (base::GetBoolProperty("dalvik.vm.useartservice", false)) {
+            GTEST_SKIP() << "Skipping legacy dexopt tests when ART Service is enabled";
+        }
+
         DexoptTest::SetUp();
         CompilePrimaryDexOk("verify",
                             DEXOPT_BOOTCOMPLETE | DEXOPT_PUBLIC,
@@ -956,6 +992,10 @@
     }
 
     virtual void TearDown() {
+        if (base::GetBoolProperty("dalvik.vm.useartservice", false)) {
+            GTEST_SKIP();
+        }
+
         first_compilation_odex_fd_.reset(-1);
         first_compilation_vdex_fd_.reset(-1);
         DexoptTest::TearDown();
@@ -978,6 +1018,10 @@
 
 class ReconcileTest : public DexoptTest {
     virtual void SetUp() {
+        if (base::GetBoolProperty("dalvik.vm.useartservice", false)) {
+            GTEST_SKIP() << "Skipping legacy dexopt tests when ART Service is enabled";
+        }
+
         DexoptTest::SetUp();
         CompileSecondaryDex(secondary_dex_ce_, DEXOPT_STORAGE_CE,
             /*binder_ok*/ true, /*compile_ok*/ true);
@@ -1043,6 +1087,10 @@
     static constexpr const char* kPrimaryProfile = "primary.prof";
 
     virtual void SetUp() {
+        if (base::GetBoolProperty("dalvik.vm.useartservice", false)) {
+            GTEST_SKIP() << "Skipping legacy dexopt tests when ART Service is enabled";
+        }
+
         DexoptTest::SetUp();
         cur_profile_ = create_current_profile_path(
                 kTestUserId, package_name_, kPrimaryProfile, /*is_secondary_dex*/ false);
@@ -1143,7 +1191,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);
@@ -1416,6 +1464,9 @@
     std::vector<int64_t> extra_ce_data_inodes_;
 
     virtual void SetUp() {
+        if (base::GetBoolProperty("dalvik.vm.useartservice", false)) {
+            GTEST_SKIP() << "Skipping legacy dexopt tests when ART Service is enabled";
+        }
 
         ProfileTest::SetUp();
         intial_android_profiles_dir = android_profiles_dir;
@@ -1429,6 +1480,10 @@
     }
 
     virtual void TearDown() {
+        if (base::GetBoolProperty("dalvik.vm.useartservice", false)) {
+            GTEST_SKIP();
+        }
+
         android_profiles_dir = intial_android_profiles_dir;
         deleteAppProfilesForBootMerge();
         ProfileTest::TearDown();
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/installd/utils_default.cpp b/cmds/installd/utils_default.cpp
index a6025e6..85ce450 100644
--- a/cmds/installd/utils_default.cpp
+++ b/cmds/installd/utils_default.cpp
@@ -23,7 +23,7 @@
 // platform dependent logic.
 
 int rm_package_dir(const std::string& package_dir) {
-    return delete_dir_contents_and_dir(package_dir);
+    return rename_delete_dir_contents_and_dir(package_dir);
 }
 
 }  // namespace installd
diff --git a/cmds/servicemanager/Android.bp b/cmds/servicemanager/Android.bp
index 1386660..fb69513 100644
--- a/cmds/servicemanager/Android.bp
+++ b/cmds/servicemanager/Android.bp
@@ -72,6 +72,7 @@
 
 cc_test {
     name: "servicemanager_test",
+    host_supported: true,
     test_suites: ["device-tests"],
     defaults: ["servicemanager_defaults"],
     srcs: [
@@ -92,9 +93,22 @@
         libfuzzer_options: [
             "max_len=50000",
         ],
-        cc: [
-            "smoreland@google.com",
-            "waghpawan@google.com",
+    },
+}
+
+// Adding this new fuzzer to test the corpus generated by record_binder
+cc_fuzz {
+    name: "servicemanager_test_fuzzer",
+    defaults: [
+        "servicemanager_defaults",
+        "service_fuzzer_defaults",
+    ],
+    host_supported: true,
+    srcs: ["fuzzers/ServiceManagerTestFuzzer.cpp"],
+    fuzz_config: {
+        libfuzzer_options: [
+            "max_len=50000",
         ],
     },
+    corpus: ["fuzzers/servicemamanager_fuzzer_corpus/*"],
 }
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index 07809e2..63f3821 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";
     }
 }
@@ -332,34 +337,38 @@
     auto ctx = mAccess->getCallingContext();
 
     if (multiuser_get_app_id(ctx.uid) >= AID_APP) {
-        return Status::fromExceptionCode(Status::EX_SECURITY, "App UIDs cannot add services");
+        return Status::fromExceptionCode(Status::EX_SECURITY, "App UIDs cannot add services.");
     }
 
     if (!mAccess->canAdd(ctx, name)) {
-        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denial");
+        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
     }
 
     if (binder == nullptr) {
-        return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Null binder");
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Null binder.");
     }
 
     if (!isValidServiceName(name)) {
         ALOGE("Invalid service name: %s", name.c_str());
-        return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Invalid service name");
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Invalid service name.");
     }
 
 #ifndef VENDORSERVICEMANAGER
     if (!meetsDeclarationRequirements(binder, name)) {
         // already logged
-        return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "VINTF declaration error");
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "VINTF declaration error.");
     }
 #endif  // !VENDORSERVICEMANAGER
 
+    if ((dumpPriority & DUMP_FLAG_PRIORITY_ALL) == 0) {
+        ALOGW("Dump flag priority is not set when adding %s", name.c_str());
+    }
+
     // implicitly unlinked when the binder is removed
     if (binder->remoteBinder() != nullptr &&
         binder->linkToDeath(sp<ServiceManager>::fromExisting(this)) != OK) {
         ALOGE("Could not linkToDeath when adding %s", name.c_str());
-        return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "linkToDeath failure");
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "Couldn't linkToDeath.");
     }
 
     auto it = mNameToService.find(name);
@@ -413,7 +422,7 @@
 
 Status ServiceManager::listServices(int32_t dumpPriority, std::vector<std::string>* outList) {
     if (!mAccess->canList(mAccess->getCallingContext())) {
-        return Status::fromExceptionCode(Status::EX_SECURITY);
+        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
     }
 
     size_t toReserve = 0;
@@ -457,18 +466,18 @@
 
     if (!isValidServiceName(name)) {
         ALOGE("Invalid service name: %s", name.c_str());
-        return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT);
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Invalid service name.");
     }
 
     if (callback == nullptr) {
-        return Status::fromExceptionCode(Status::EX_NULL_POINTER);
+        return Status::fromExceptionCode(Status::EX_NULL_POINTER, "Null callback.");
     }
 
     if (OK !=
         IInterface::asBinder(callback)->linkToDeath(
                 sp<ServiceManager>::fromExisting(this))) {
         ALOGE("Could not linkToDeath when adding %s", name.c_str());
-        return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "Couldn't link to death.");
     }
 
     mNameToRegistrationCallback[name].push_back(callback);
@@ -488,7 +497,7 @@
     auto ctx = mAccess->getCallingContext();
 
     if (!mAccess->canFind(ctx, name)) {
-        return Status::fromExceptionCode(Status::EX_SECURITY);
+        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
     }
 
     bool found = false;
@@ -500,7 +509,7 @@
 
     if (!found) {
         ALOGE("Trying to unregister callback, but none exists %s", name.c_str());
-        return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "Nothing to unregister.");
     }
 
     return Status::ok();
@@ -510,7 +519,7 @@
     auto ctx = mAccess->getCallingContext();
 
     if (!mAccess->canFind(ctx, name)) {
-        return Status::fromExceptionCode(Status::EX_SECURITY);
+        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
     }
 
     *outReturn = false;
@@ -538,7 +547,7 @@
     }
 
     if (outReturn->size() == 0 && allInstances.size() != 0) {
-        return Status::fromExceptionCode(Status::EX_SECURITY);
+        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
     }
 
     return Status::ok();
@@ -549,7 +558,7 @@
     auto ctx = mAccess->getCallingContext();
 
     if (!mAccess->canFind(ctx, name)) {
-        return Status::fromExceptionCode(Status::EX_SECURITY);
+        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
     }
 
     *outReturn = std::nullopt;
@@ -578,7 +587,7 @@
     }
 
     if (outReturn->size() == 0 && apexUpdatableInstances.size() != 0) {
-        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denial");
+        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
     }
 
     return Status::ok();
@@ -589,7 +598,7 @@
     auto ctx = mAccess->getCallingContext();
 
     if (!mAccess->canFind(ctx, name)) {
-        return Status::fromExceptionCode(Status::EX_SECURITY);
+        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
     }
 
     *outReturn = std::nullopt;
@@ -658,36 +667,42 @@
 Status ServiceManager::registerClientCallback(const std::string& name, const sp<IBinder>& service,
                                               const sp<IClientCallback>& cb) {
     if (cb == nullptr) {
-        return Status::fromExceptionCode(Status::EX_NULL_POINTER);
+        return Status::fromExceptionCode(Status::EX_NULL_POINTER, "Callback null.");
     }
 
     auto ctx = mAccess->getCallingContext();
     if (!mAccess->canAdd(ctx, name)) {
-        return Status::fromExceptionCode(Status::EX_SECURITY);
+        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
     }
 
     auto serviceIt = mNameToService.find(name);
     if (serviceIt == mNameToService.end()) {
         ALOGE("Could not add callback for nonexistent service: %s", name.c_str());
-        return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT);
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Service doesn't exist.");
     }
 
     if (serviceIt->second.ctx.debugPid != IPCThreadState::self()->getCallingPid()) {
         ALOGW("Only a server can register for client callbacks (for %s)", name.c_str());
-        return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION);
+        return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION,
+                                         "Only service can register client callback for itself.");
     }
 
     if (serviceIt->second.binder != service) {
         ALOGW("Tried to register client callback for %s but a different service is registered "
               "under this name.",
               name.c_str());
-        return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT);
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Service mismatch.");
     }
 
     if (OK !=
         IInterface::asBinder(cb)->linkToDeath(sp<ServiceManager>::fromExisting(this))) {
         ALOGE("Could not linkToDeath when adding client callback for %s", name.c_str());
-        return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "Couldn't linkToDeath.");
+    }
+
+    // make sure all callbacks have been told about a consistent state - b/278038751
+    if (serviceIt->second.hasClients) {
+        cb->onClients(service, true);
     }
 
     mNameToClientCallback[name].push_back(cb);
@@ -801,24 +816,25 @@
 
 Status ServiceManager::tryUnregisterService(const std::string& name, const sp<IBinder>& binder) {
     if (binder == nullptr) {
-        return Status::fromExceptionCode(Status::EX_NULL_POINTER);
+        return Status::fromExceptionCode(Status::EX_NULL_POINTER, "Null service.");
     }
 
     auto ctx = mAccess->getCallingContext();
     if (!mAccess->canAdd(ctx, name)) {
-        return Status::fromExceptionCode(Status::EX_SECURITY);
+        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
     }
 
     auto serviceIt = mNameToService.find(name);
     if (serviceIt == mNameToService.end()) {
         ALOGW("Tried to unregister %s, but that service wasn't registered to begin with.",
               name.c_str());
-        return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "Service not registered.");
     }
 
     if (serviceIt->second.ctx.debugPid != IPCThreadState::self()->getCallingPid()) {
         ALOGW("Only a server can unregister itself (for %s)", name.c_str());
-        return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION);
+        return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION,
+                                         "Service can only unregister itself.");
     }
 
     sp<IBinder> storedBinder = serviceIt->second.binder;
@@ -826,14 +842,16 @@
     if (binder != storedBinder) {
         ALOGW("Tried to unregister %s, but a different service is registered under this name.",
               name.c_str());
-        return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE,
+                                         "Different service registered under this name.");
     }
 
     // important because we don't have timer-based guarantees, we don't want to clear
     // this
     if (serviceIt->second.guaranteeClient) {
         ALOGI("Tried to unregister %s, but there is about to be a client.", name.c_str());
-        return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE,
+                                         "Can't unregister, pending client.");
     }
 
     // - kernel driver will hold onto one refcount (during this transaction)
@@ -848,7 +866,8 @@
         // help reduce thrashing, but we should be able to remove it.
         serviceIt->second.guaranteeClient = true;
 
-        return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE,
+                                         "Can't unregister, known client.");
     }
 
     ALOGI("Unregistering %s", name.c_str());
@@ -859,7 +878,7 @@
 
 Status ServiceManager::getServiceDebugInfo(std::vector<ServiceDebugInfo>* outReturn) {
     if (!mAccess->canList(mAccess->getCallingContext())) {
-        return Status::fromExceptionCode(Status::EX_SECURITY);
+        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
     }
 
     outReturn->reserve(mNameToService.size());
diff --git a/cmds/servicemanager/fuzzers/ServiceManagerTestFuzzer.cpp b/cmds/servicemanager/fuzzers/ServiceManagerTestFuzzer.cpp
new file mode 100644
index 0000000..e19b6eb
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/ServiceManagerTestFuzzer.cpp
@@ -0,0 +1,48 @@
+/*
+ * 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 <fuzzbinder/libbinder_driver.h>
+#include <utils/StrongPointer.h>
+
+#include "Access.h"
+#include "ServiceManager.h"
+
+using ::android::Access;
+using ::android::Parcel;
+using ::android::ServiceManager;
+using ::android::sp;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    FuzzedDataProvider provider(data, size);
+    auto accessPtr = std::make_unique<Access>();
+    auto serviceManager = sp<ServiceManager>::make(std::move(accessPtr));
+
+    // Reserved bytes
+    provider.ConsumeBytes<uint8_t>(8);
+    uint32_t code = provider.ConsumeIntegral<uint32_t>();
+    uint32_t flag = provider.ConsumeIntegral<uint32_t>();
+    std::vector<uint8_t> parcelData = provider.ConsumeRemainingBytes<uint8_t>();
+
+    Parcel inputParcel;
+    inputParcel.setData(parcelData.data(), parcelData.size());
+
+    Parcel reply;
+    serviceManager->transact(code, inputParcel, &reply, flag);
+
+    serviceManager->clear();
+
+    return 0;
+}
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_1 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_1
new file mode 100644
index 0000000..39e5104
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_1
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_10 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_10
new file mode 100644
index 0000000..07319f8
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_10
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_11 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_11
new file mode 100644
index 0000000..39e5104
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_11
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_12 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_12
new file mode 100644
index 0000000..07319f8
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_12
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_13 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_13
new file mode 100644
index 0000000..39e5104
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_13
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_14 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_14
new file mode 100644
index 0000000..07319f8
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_14
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_15 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_15
new file mode 100644
index 0000000..39e5104
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_15
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_16 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_16
new file mode 100644
index 0000000..07319f8
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_16
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_17 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_17
new file mode 100644
index 0000000..39e5104
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_17
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_18 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_18
new file mode 100644
index 0000000..88ad474
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_18
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_19 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_19
new file mode 100644
index 0000000..fae15a2
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_19
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_2 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_2
new file mode 100644
index 0000000..e69ab49
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_2
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_20 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_20
new file mode 100644
index 0000000..39e5104
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_20
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_21 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_21
new file mode 100644
index 0000000..88ad474
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_21
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_22 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_22
new file mode 100644
index 0000000..fae15a2
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_22
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_23 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_23
new file mode 100644
index 0000000..39e5104
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_23
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_24 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_24
new file mode 100644
index 0000000..88ad474
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_24
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_25 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_25
new file mode 100644
index 0000000..fae15a2
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_25
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_26 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_26
new file mode 100644
index 0000000..39e5104
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_26
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_27 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_27
new file mode 100644
index 0000000..88ad474
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_27
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_28 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_28
new file mode 100644
index 0000000..fae15a2
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_28
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_29 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_29
new file mode 100644
index 0000000..39e5104
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_29
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_3 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_3
new file mode 100644
index 0000000..39e5104
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_3
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_30 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_30
new file mode 100644
index 0000000..88ad474
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_30
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_31 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_31
new file mode 100644
index 0000000..fae15a2
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_31
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_32 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_32
new file mode 100644
index 0000000..39e5104
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_32
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_33 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_33
new file mode 100644
index 0000000..88ad474
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_33
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_34 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_34
new file mode 100644
index 0000000..fae15a2
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_34
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_35 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_35
new file mode 100644
index 0000000..39e5104
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_35
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_36 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_36
new file mode 100644
index 0000000..88ad474
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_36
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_37 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_37
new file mode 100644
index 0000000..fae15a2
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_37
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_38 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_38
new file mode 100644
index 0000000..39e5104
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_38
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_39 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_39
new file mode 100644
index 0000000..b326907
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_39
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_4 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_4
new file mode 100644
index 0000000..05b27bf
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_4
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_40 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_40
new file mode 100644
index 0000000..39e5104
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_40
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_41 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_41
new file mode 100644
index 0000000..b326907
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_41
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_42 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_42
new file mode 100644
index 0000000..cdaa1f0
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_42
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_43 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_43
new file mode 100644
index 0000000..ff0941b
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_43
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_44 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_44
new file mode 100644
index 0000000..cdaa1f0
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_44
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_45 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_45
new file mode 100644
index 0000000..39e5104
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_45
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_46 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_46
new file mode 100644
index 0000000..7e5f948
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_46
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_5 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_5
new file mode 100644
index 0000000..39e5104
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_5
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_6 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_6
new file mode 100644
index 0000000..07319f8
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_6
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_7 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_7
new file mode 100644
index 0000000..39e5104
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_7
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_8 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_8
new file mode 100644
index 0000000..07319f8
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_8
Binary files differ
diff --git a/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_9 b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_9
new file mode 100644
index 0000000..39e5104
--- /dev/null
+++ b/cmds/servicemanager/fuzzers/servicemanager_fuzzer_corpus/Transaction_9
Binary files differ
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index a737bd3..754e7b2 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -77,6 +77,12 @@
 }
 
 prebuilt_etc {
+    name: "android.hardware.context_hub.prebuilt.xml",
+    src: "android.hardware.context_hub.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "android.hardware.ethernet.prebuilt.xml",
     src: "android.hardware.ethernet.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
@@ -107,30 +113,102 @@
 }
 
 prebuilt_etc {
+    name: "android.hardware.sensor.accelerometer_limited_axes_uncalibrated.prebuilt.xml",
+    src: "android.hardware.sensor.accelerometer_limited_axes_uncalibrated.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
+    name: "android.hardware.sensor.accelerometer_limited_axes.prebuilt.xml",
+    src: "android.hardware.sensor.accelerometer_limited_axes.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
+    name: "android.hardware.sensor.accelerometer.prebuilt.xml",
+    src: "android.hardware.sensor.accelerometer.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "android.hardware.sensor.ambient_temperature.prebuilt.xml",
     src: "android.hardware.sensor.ambient_temperature.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
 }
 
 prebuilt_etc {
+    name: "android.hardware.sensor.assist.prebuilt.xml",
+    src: "android.hardware.sensor.assist.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "android.hardware.sensor.barometer.prebuilt.xml",
     src: "android.hardware.sensor.barometer.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
 }
 
 prebuilt_etc {
+    name: "android.hardware.sensor.compass.prebuilt.xml",
+    src: "android.hardware.sensor.compass.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "android.hardware.sensor.dynamic.head_tracker.prebuilt.xml",
     src: "android.hardware.sensor.dynamic.head_tracker.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
 }
 
 prebuilt_etc {
+    name: "android.hardware.sensor.gyroscope_limited_axes_uncalibrated.prebuilt.xml",
+    src: "android.hardware.sensor.gyroscope_limited_axes_uncalibrated.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
+    name: "android.hardware.sensor.gyroscope_limited_axes.prebuilt.xml",
+    src: "android.hardware.sensor.gyroscope_limited_axes.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "android.hardware.sensor.gyroscope.prebuilt.xml",
     src: "android.hardware.sensor.gyroscope.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
 }
 
 prebuilt_etc {
+    name: "android.hardware.sensor.heading.prebuilt.xml",
+    src: "android.hardware.sensor.heading.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
+    name: "android.hardware.sensor.heartrate.ecg.prebuilt.xml",
+    src: "android.hardware.sensor.heartrate.ecg.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
+    name: "android.hardware.sensor.heartrate.fitness.prebuilt.xml",
+    src: "android.hardware.sensor.heartrate.fitness.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
+    name: "android.hardware.sensor.heartrate.prebuilt.xml",
+    src: "android.hardware.sensor.heartrate.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
+    name: "android.hardware.sensor.hifi_sensors.prebuilt.xml",
+    src: "android.hardware.sensor.hifi_sensors.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "android.hardware.sensor.hinge_angle.prebuilt.xml",
     src: "android.hardware.sensor.hinge_angle.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
@@ -155,6 +233,18 @@
 }
 
 prebuilt_etc {
+    name: "android.hardware.sensor.stepcounter.prebuilt.xml",
+    src: "android.hardware.sensor.stepcounter.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
+    name: "android.hardware.sensor.stepdetector.prebuilt.xml",
+    src: "android.hardware.sensor.stepdetector.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "android.hardware.telephony.gsm.prebuilt.xml",
     src: "android.hardware.telephony.gsm.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
index 5966cba..d36c958 100644
--- a/data/etc/android.hardware.telephony.satellite.xml
+++ b/data/etc/android.hardware.telephony.satellite.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<!-- Feature for devices that support Satellite communication via Satellite HAL APIs. -->
+<!-- Feature for devices that support satellite communication via satellite vendor service APIs. -->
 <permissions>
     <feature name="android.hardware.telephony.satellite" />
 </permissions>
diff --git a/headers/media_plugin/media/hardware/VideoAPI.h b/headers/media_plugin/media/hardware/VideoAPI.h
index a090876..5466680 100644
--- a/headers/media_plugin/media/hardware/VideoAPI.h
+++ b/headers/media_plugin/media/hardware/VideoAPI.h
@@ -127,6 +127,8 @@
         PrimariesBT601_6_525,   // Rec.ITU-R BT.601-6 525 or equivalent
         PrimariesGenericFilm,   // Generic Film
         PrimariesBT2020,        // Rec.ITU-R BT.2020 or equivalent
+        PrimariesRP431,         // SMPTE RP 431-2 (DCI P3)
+        PrimariesEG432,         // SMPTE EG 432-1 (Display P3)
         PrimariesOther = 0xff,
     };
 
@@ -173,6 +175,8 @@
         StandardBT2020Constant,         // PrimariesBT2020 and MatrixBT2020Constant
         StandardBT470M,                 // PrimariesBT470_6M and MatrixBT470_6M
         StandardFilm,                   // PrimariesGenericFilm and KR=0.253, KB=0.068
+        StandardDisplayP3,              // PrimariesEG432 and MatrixBT601_6
+        // StandardAdobeRGB,  // for placeholder only (not used by media)
         StandardOther = 0xff,
     };
 
@@ -282,6 +286,8 @@
         case ColorAspects::PrimariesBT601_6_525: return "BT601_6_525";
         case ColorAspects::PrimariesGenericFilm: return "GenericFilm";
         case ColorAspects::PrimariesBT2020:      return "BT2020";
+        case ColorAspects::PrimariesRP431:       return "RP431";
+        case ColorAspects::PrimariesEG432:       return "EG432";
         case ColorAspects::PrimariesOther:       return "Other";
         default:                                 return def;
     }
@@ -332,6 +338,8 @@
         case ColorAspects::StandardBT2020Constant:       return "BT2020Constant";
         case ColorAspects::StandardBT470M:               return "BT470M";
         case ColorAspects::StandardFilm:                 return "Film";
+        case ColorAspects::StandardDisplayP3:            return "DisplayP3";
+        // case ColorAspects::StandardAdobeRGB:             return "AdobeRGB";
         case ColorAspects::StandardOther:                return "Other";
         default:                                         return def;
     }
diff --git a/include/android/input.h b/include/android/input.h
index d6f9d63..a45f065 100644
--- a/include/android/input.h
+++ b/include/android/input.h
@@ -256,13 +256,13 @@
     AKEY_EVENT_FLAG_LONG_PRESS = 0x80,
 
     /**
-     * Set when a key event has AKEY_EVENT_FLAG_CANCELED set because a long
+     * Set when a key event has #AKEY_EVENT_FLAG_CANCELED set because a long
      * press action was executed while it was down.
      */
     AKEY_EVENT_FLAG_CANCELED_LONG_PRESS = 0x100,
 
     /**
-     * Set for AKEY_EVENT_ACTION_UP when this event's key code is still being
+     * Set for #AKEY_EVENT_ACTION_UP when this event's key code is still being
      * tracked from its initial down.  That is, somebody requested that tracking
      * started on the key down and a long press has not caused
      * the tracking to be canceled.
@@ -282,7 +282,7 @@
 
 /**
  * Bit shift for the action bits holding the pointer index as
- * defined by AMOTION_EVENT_ACTION_POINTER_INDEX_MASK.
+ * defined by #AMOTION_EVENT_ACTION_POINTER_INDEX_MASK.
  */
 #define AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT 8
 
@@ -293,8 +293,8 @@
 
     /**
      * Bits in the action code that represent a pointer index, used with
-     * AMOTION_EVENT_ACTION_POINTER_DOWN and AMOTION_EVENT_ACTION_POINTER_UP.  Shifting
-     * down by AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT provides the actual pointer
+     * #AMOTION_EVENT_ACTION_POINTER_DOWN and AMOTION_EVENT_ACTION_POINTER_UP.  Shifting
+     * down by #AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT provides the actual pointer
      * index where the data for the pointer going up or down can be found.
      */
     AMOTION_EVENT_ACTION_POINTER_INDEX_MASK  = 0xff00,
@@ -309,8 +309,8 @@
     AMOTION_EVENT_ACTION_UP = 1,
 
     /**
-     * A change has happened during a press gesture (between AMOTION_EVENT_ACTION_DOWN and
-     * AMOTION_EVENT_ACTION_UP).  The motion contains the most recent point, as well as
+     * A change has happened during a press gesture (between #AMOTION_EVENT_ACTION_DOWN and
+     * #AMOTION_EVENT_ACTION_UP).  The motion contains the most recent point, as well as
      * any intermediate points since the last down or move event.
      */
     AMOTION_EVENT_ACTION_MOVE = 2,
@@ -330,18 +330,18 @@
 
     /**
      * A non-primary pointer has gone down.
-     * The bits in AMOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed.
+     * The bits in #AMOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed.
      */
     AMOTION_EVENT_ACTION_POINTER_DOWN = 5,
 
     /**
      * A non-primary pointer has gone up.
-     * The bits in AMOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed.
+     * The bits in #AMOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed.
      */
     AMOTION_EVENT_ACTION_POINTER_UP = 6,
 
     /**
-     * A change happened but the pointer is not down (unlike AMOTION_EVENT_ACTION_MOVE).
+     * A change happened but the pointer is not down (unlike #AMOTION_EVENT_ACTION_MOVE).
      * The motion contains the most recent point, as well as any intermediate points since
      * the last hover move event.
      */
@@ -349,8 +349,8 @@
 
     /**
      * The motion event contains relative vertical and/or horizontal scroll offsets.
-     * Use getAxisValue to retrieve the information from AMOTION_EVENT_AXIS_VSCROLL
-     * and AMOTION_EVENT_AXIS_HSCROLL.
+     * Use {@link AMotionEvent_getAxisValue} to retrieve the information from
+     * #AMOTION_EVENT_AXIS_VSCROLL and #AMOTION_EVENT_AXIS_HSCROLL.
      * The pointer may or may not be down when this event is dispatched.
      * This action is always delivered to the winder under the pointer, which
      * may not be the window currently touched.
@@ -535,7 +535,7 @@
      * is pointing in relation to the vertical axis of the current orientation of the screen.
      * The range is from -PI radians to PI radians, where 0 is pointing up,
      * -PI/2 radians is pointing left, -PI or PI radians is pointing down, and PI/2 radians
-     * is pointing right.  See also {@link AMOTION_EVENT_AXIS_TILT}.
+     * is pointing right.  See also #AMOTION_EVENT_AXIS_TILT.
      */
     AMOTION_EVENT_AXIS_ORIENTATION = 8,
     /**
@@ -688,7 +688,7 @@
     /**
      * Axis constant: The movement of y position of a motion event.
      *
-     * Same as {@link AMOTION_EVENT_AXIS_RELATIVE_X}, but for y position.
+     * Same as #AMOTION_EVENT_AXIS_RELATIVE_X, but for y position.
      */
     AMOTION_EVENT_AXIS_RELATIVE_Y = 28,
     /**
@@ -883,7 +883,8 @@
      * Classification constant: Ambiguous gesture.
      *
      * The user's intent with respect to the current event stream is not yet determined. Events
-     * starting in AMBIGUOUS_GESTURE will eventually resolve into either DEEP_PRESS or NONE.
+     * starting in #AMOTION_EVENT_CLASSIFICATION_AMBIGUOUS_GESTURE will eventually resolve into
+     * either #AMOTION_EVENT_CLASSIFICATION_DEEP_PRESS or #AMOTION_EVENT_CLASSIFICATION_NONE.
      * Gestural actions, such as scrolling, should be inhibited until the classification resolves
      * to another value or the event stream ends.
      */
@@ -1006,7 +1007,8 @@
  * Refer to the documentation on android.view.InputDevice for more details about input sources
  * and their correct interpretation.
  *
- * @deprecated These constants are deprecated. Use {@link AMOTION_EVENT_AXIS AMOTION_EVENT_AXIS_*} constants instead.
+ * @deprecated These constants are deprecated. Use {@link AMOTION_EVENT_AXIS AMOTION_EVENT_AXIS_*}
+ * constants instead.
  */
 enum {
     /** x */
@@ -1060,8 +1062,8 @@
 /**
  * Releases interface objects created by {@link AKeyEvent_fromJava()}
  * and {@link AMotionEvent_fromJava()}.
- * After returning, the specified AInputEvent* object becomes invalid and should no longer be used.
- * The underlying Java object remains valid and does not change its state.
+ * After returning, the specified {@link AInputEvent}* object becomes invalid and should no longer
+ * be used. The underlying Java object remains valid and does not change its state.
  *
  * Available since API level 31.
  */
@@ -1114,9 +1116,10 @@
 int64_t AKeyEvent_getEventTime(const AInputEvent* key_event);
 
 /**
- * Creates a native AInputEvent* object that is a copy of the specified Java android.view.KeyEvent.
- * The result may be used with generic and KeyEvent-specific AInputEvent_* functions. The object
- * returned by this function must be disposed using {@link AInputEvent_release()}.
+ * Creates a native {@link AInputEvent}* object that is a copy of the specified Java
+ * android.view.KeyEvent. The result may be used with generic and KeyEvent-specific AInputEvent_*
+ * functions. The object returned by this function must be disposed using
+ * {@link AInputEvent_release()}.
  *
  * Available since API level 31.
  */
@@ -1304,7 +1307,7 @@
 /**
  * Get the number of historical points in this event.  These are movements that
  * have occurred between this event and the previous event.  This only applies
- * to AMOTION_EVENT_ACTION_MOVE events -- all other actions will have a size of 0.
+ * to #AMOTION_EVENT_ACTION_MOVE events -- all other actions will have a size of 0.
  * Historical samples are indexed from oldest to newest.
  */
 size_t AMotionEvent_getHistorySize(const AInputEvent* motion_event);
@@ -1465,7 +1468,7 @@
         __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
- * Creates a native AInputEvent* object that is a copy of the specified Java
+ * Creates a native {@link AInputEvent}* object that is a copy of the specified Java
  * android.view.MotionEvent. The result may be used with generic and MotionEvent-specific
  * AInputEvent_* functions. The object returned by this function must be disposed using
  * {@link AInputEvent_release()}.
@@ -1485,7 +1488,7 @@
 
 /**
  * Add this input queue to a looper for processing.  See
- * ALooper_addFd() for information on the ident, callback, and data params.
+ * {@link ALooper_addFd()} for information on the ident, callback, and data params.
  */
 void AInputQueue_attachLooper(AInputQueue* queue, ALooper* looper,
         int ident, ALooper_callbackFunc callback, void* data);
@@ -1520,12 +1523,12 @@
 
 /**
  * Report that dispatching has finished with the given event.
- * This must be called after receiving an event with AInputQueue_get_event().
+ * This must be called after receiving an event with {@link AInputQueue_getEvent()}.
  */
 void AInputQueue_finishEvent(AInputQueue* queue, AInputEvent* event, int handled);
 
 /**
- * Returns the AInputQueue* object associated with the supplied Java InputQueue
+ * Returns the {@link AInputQueue}* object associated with the supplied Java InputQueue
  * object. The returned native object holds a weak reference to the Java object,
  * and is only valid as long as the Java object has not yet been disposed. You
  * should ensure that there is a strong reference to the Java object and that it
diff --git a/include/android/keycodes.h b/include/android/keycodes.h
index d4ba321..f8fb256 100644
--- a/include/android/keycodes.h
+++ b/include/android/keycodes.h
@@ -831,6 +831,14 @@
     AKEYCODE_STYLUS_BUTTON_TAIL = 311,
     /** Key to open recent apps (a.k.a. Overview) */
     AKEYCODE_RECENT_APPS = 312,
+    /** User customizable key #1. */
+    AKEYCODE_MACRO_1 = 313,
+    /** User customizable key #2. */
+    AKEYCODE_MACRO_2 = 314,
+    /** User customizable key #3. */
+    AKEYCODE_MACRO_3 = 315,
+    /** User customizable key #4. */
+    AKEYCODE_MACRO_4 = 316,
 
     // NOTE: If you add a new keycode here you must also add it to several other files.
     //       Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
diff --git a/include/android/surface_control.h b/include/android/surface_control.h
index e4926a6..cce2e46 100644
--- a/include/android/surface_control.h
+++ b/include/android/surface_control.h
@@ -54,6 +54,12 @@
  * The caller takes ownership of the ASurfaceControl returned and must release it
  * using ASurfaceControl_release below.
  *
+ * By default the \a ASurfaceControl will be visible and display any buffer submitted. In
+ * addition, the default buffer submission control may release and not display all buffers
+ * that are submitted before receiving a callback for the previous buffer. See
+ * \a ASurfaceTransaction_setVisibility and \a ASurfaceTransaction_setEnableBackPressure to
+ * change the default behaviors after creation.
+ *
  * Available since API level 29.
  */
 ASurfaceControl* ASurfaceControl_createFromWindow(ANativeWindow* parent, const char* debug_name)
@@ -548,13 +554,19 @@
  *                     to the max display brightness. The system may not be able to, or may choose
  *                     not to, deliver the requested range.
  *
- *                     If unspecified, the system will attempt to provide the best range it can
- *                     for the given ambient conditions & device state. However, voluntarily
- *                     reducing the requested range can help improve battery life as well as can
- *                     improve quality by ensuring greater bit depth is allocated to the luminance
- *                     range in use.
+ *                     While requesting a large desired ratio will result in the most
+ *                     dynamic range, voluntarily reducing the requested range can help
+ *                     improve battery life as well as can improve quality by ensuring
+ *                     greater bit depth is allocated to the luminance range in use.
+ *
+ *                     Default value is 1.0f and indicates that extended range brightness
+ *                     is not being used, so the resulting SDR or HDR behavior will be
+ *                     determined entirely by the dataspace being used (ie, typically SDR
+ *                     however PQ or HLG transfer functions will still result in HDR)
  *
  *                     Must be finite && >= 1.0f
+ *
+ * Available since API level 34.
  */
 void ASurfaceTransaction_setExtendedRangeBrightness(ASurfaceTransaction* transaction,
                                             ASurfaceControl* surface_control,
diff --git a/include/ftl/algorithm.h b/include/ftl/algorithm.h
index c5ff03b..c0f6768 100644
--- a/include/ftl/algorithm.h
+++ b/include/ftl/algorithm.h
@@ -68,4 +68,29 @@
   return std::cref(pair.second);
 }
 
+// Combinator for ftl::Optional<T>::or_else when T is std::reference_wrapper<const V>. Given a
+// lambda argument that returns a `constexpr` value, ftl::static_ref<T> binds a reference to a
+// static T initialized to that constant.
+//
+//   const ftl::SmallMap map = ftl::init::map(13, "tiramisu"sv)(14, "upside-down cake"sv);
+//   assert("???"sv ==
+//          map.get(20).or_else(ftl::static_ref<std::string_view>([] { return "???"sv; }))->get());
+//
+//   using Map = decltype(map);
+//
+//   assert("snow cone"sv ==
+//          ftl::find_if(map, [](const auto& pair) { return pair.second.front() == 's'; })
+//              .transform(ftl::to_mapped_ref<Map>)
+//              .or_else(ftl::static_ref<std::string_view>([] { return "snow cone"sv; }))
+//              ->get());
+//
+template <typename T, typename F>
+constexpr auto static_ref(F&& f) {
+  return [f = std::forward<F>(f)] {
+    constexpr auto kInitializer = f();
+    static const T kValue = kInitializer;
+    return Optional(std::cref(kValue));
+  };
+}
+
 }  // namespace android::ftl
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/flags.h b/include/ftl/flags.h
index cdb4e84..dbe3148 100644
--- a/include/ftl/flags.h
+++ b/include/ftl/flags.h
@@ -120,7 +120,7 @@
     }
 
     /* Tests whether any of the given flags are set */
-    bool any(Flags<F> f) const { return (mFlags & f.mFlags) != 0; }
+    bool any(Flags<F> f = ~Flags<F>()) const { return (mFlags & f.mFlags) != 0; }
 
     /* Tests whether all of the given flags are set */
     bool all(Flags<F> f) const { return (mFlags & f.mFlags) == f.mFlags; }
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..1e810b4 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -210,13 +210,40 @@
  */
 float transformAngle(const ui::Transform& transform, float angleRadians);
 
-const char* inputEventTypeToString(int32_t type);
+/**
+ * The type of the InputEvent.
+ * This should have 1:1 correspondence with the values of anonymous enum defined in input.h.
+ */
+enum class InputEventType {
+    KEY = AINPUT_EVENT_TYPE_KEY,
+    MOTION = AINPUT_EVENT_TYPE_MOTION,
+    FOCUS = AINPUT_EVENT_TYPE_FOCUS,
+    CAPTURE = AINPUT_EVENT_TYPE_CAPTURE,
+    DRAG = AINPUT_EVENT_TYPE_DRAG,
+    TOUCH_MODE = AINPUT_EVENT_TYPE_TOUCH_MODE,
+    ftl_first = KEY,
+    ftl_last = TOUCH_MODE,
+};
 
 std::string inputEventSourceToString(int32_t source);
 
 bool isFromSource(uint32_t source, uint32_t test);
 
-bool isStylusToolType(uint32_t toolType);
+/**
+ * The pointer tool type.
+ */
+enum class ToolType {
+    UNKNOWN = AMOTION_EVENT_TOOL_TYPE_UNKNOWN,
+    FINGER = AMOTION_EVENT_TOOL_TYPE_FINGER,
+    STYLUS = AMOTION_EVENT_TOOL_TYPE_STYLUS,
+    MOUSE = AMOTION_EVENT_TOOL_TYPE_MOUSE,
+    ERASER = AMOTION_EVENT_TOOL_TYPE_ERASER,
+    PALM = AMOTION_EVENT_TOOL_TYPE_PALM,
+    ftl_first = UNKNOWN,
+    ftl_last = PALM,
+};
+
+bool isStylusToolType(ToolType toolType);
 
 /*
  * Flags that flow alongside events in the input dispatch system to help with certain
@@ -320,8 +347,6 @@
  */
 const char* motionClassificationToString(MotionClassification classification);
 
-const char* motionToolTypeToString(int32_t toolType);
-
 /**
  * Portion of FrameMetrics timeline of interest to input code.
  */
@@ -448,11 +473,11 @@
     int32_t id;
 
     // The pointer tool type.
-    int32_t toolType;
+    ToolType toolType;
 
     inline void clear() {
         id = -1;
-        toolType = 0;
+        toolType = ToolType::UNKNOWN;
     }
 
     bool operator==(const PointerProperties& other) const;
@@ -470,7 +495,7 @@
 public:
     virtual ~InputEvent() { }
 
-    virtual int32_t getType() const = 0;
+    virtual InputEventType getType() const = 0;
 
     inline int32_t getId() const { return mId; }
 
@@ -501,6 +526,8 @@
     std::array<uint8_t, 32> mHmac;
 };
 
+std::ostream& operator<<(std::ostream& out, const InputEvent& event);
+
 /*
  * Key events.
  */
@@ -508,7 +535,7 @@
 public:
     virtual ~KeyEvent() { }
 
-    virtual int32_t getType() const { return AINPUT_EVENT_TYPE_KEY; }
+    virtual InputEventType getType() const { return InputEventType::KEY; }
 
     inline int32_t getAction() const { return mAction; }
 
@@ -559,7 +586,7 @@
 public:
     virtual ~MotionEvent() { }
 
-    virtual int32_t getType() const { return AINPUT_EVENT_TYPE_MOTION; }
+    virtual InputEventType getType() const { return InputEventType::MOTION; }
 
     inline int32_t getAction() const { return mAction; }
 
@@ -638,7 +665,7 @@
         return mPointerProperties[pointerIndex].id;
     }
 
-    inline int32_t getToolType(size_t pointerIndex) const {
+    inline ToolType getToolType(size_t pointerIndex) const {
         return mPointerProperties[pointerIndex].toolType;
     }
 
@@ -855,6 +882,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;
@@ -885,7 +914,7 @@
 public:
     virtual ~FocusEvent() {}
 
-    virtual int32_t getType() const override { return AINPUT_EVENT_TYPE_FOCUS; }
+    virtual InputEventType getType() const override { return InputEventType::FOCUS; }
 
     inline bool getHasFocus() const { return mHasFocus; }
 
@@ -904,7 +933,7 @@
 public:
     virtual ~CaptureEvent() {}
 
-    virtual int32_t getType() const override { return AINPUT_EVENT_TYPE_CAPTURE; }
+    virtual InputEventType getType() const override { return InputEventType::CAPTURE; }
 
     inline bool getPointerCaptureEnabled() const { return mPointerCaptureEnabled; }
 
@@ -923,7 +952,7 @@
 public:
     virtual ~DragEvent() {}
 
-    virtual int32_t getType() const override { return AINPUT_EVENT_TYPE_DRAG; }
+    virtual InputEventType getType() const override { return InputEventType::DRAG; }
 
     inline bool isExiting() const { return mIsExiting; }
 
@@ -947,7 +976,7 @@
 public:
     virtual ~TouchModeEvent() {}
 
-    virtual int32_t getType() const override { return AINPUT_EVENT_TYPE_TOUCH_MODE; }
+    virtual InputEventType getType() const override { return InputEventType::TOUCH_MODE; }
 
     inline bool isInTouchMode() const { return mIsInTouchMode; }
 
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index 66d3435..1a40fdb 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -212,6 +212,11 @@
     std::string languageTag;
     // The layout type such as QWERTY or AZERTY.
     std::string layoutType;
+
+    inline bool operator==(const KeyboardLayoutInfo& other) const {
+        return languageTag == other.languageTag && layoutType == other.layoutType;
+    }
+    inline bool operator!=(const KeyboardLayoutInfo& other) const { return !(*this == other); }
 };
 
 // The version of the Universal Stylus Initiative (USI) protocol supported by the input device.
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h
index a1be542..4f53c36 100644
--- a/include/input/InputTransport.h
+++ b/include/input/InputTransport.h
@@ -669,7 +669,6 @@
     static void addSample(MotionEvent* event, const InputMessage* msg);
     static bool canAddSample(const Batch& batch, const InputMessage* msg);
     static ssize_t findSampleNoLaterThan(const Batch& batch, nsecs_t time);
-    static bool shouldResampleTool(int32_t toolType);
 
     static bool isTouchResamplingEnabled();
 };
diff --git a/include/input/KeyCharacterMap.h b/include/input/KeyCharacterMap.h
index c67310e..b2e8baa 100644
--- a/include/input/KeyCharacterMap.h
+++ b/include/input/KeyCharacterMap.h
@@ -26,9 +26,9 @@
 #include <android-base/result.h>
 #include <input/Input.h>
 #include <utils/Errors.h>
-#include <utils/KeyedVector.h>
 #include <utils/Tokenizer.h>
 #include <utils/Unicode.h>
+#include <map>
 
 // Maximum number of keys supported by KeyCharacterMaps
 #define MAX_KEYS 8192
@@ -152,13 +152,9 @@
     void writeToParcel(Parcel* parcel) const;
 #endif
 
-    bool operator==(const KeyCharacterMap& other) const;
+    bool operator==(const KeyCharacterMap& other) const = default;
 
-    bool operator!=(const KeyCharacterMap& other) const;
-
-    KeyCharacterMap(const KeyCharacterMap& other);
-
-    virtual ~KeyCharacterMap();
+    KeyCharacterMap(const KeyCharacterMap& other) = default;
 
 private:
     struct Behavior {
@@ -173,17 +169,18 @@
 
         /* The replacement keycode if the key has to be replaced outright. */
         int32_t replacementKeyCode = 0;
+
+        bool operator==(const Behavior&) const = default;
     };
 
     struct Key {
-        Key();
-        Key(const Key& other);
+        bool operator==(const Key&) const = default;
 
         /* The single character label printed on the key, or 0 if none. */
-        char16_t label;
+        char16_t label = 0;
 
         /* The number or symbol character generated by the key, or 0 if none. */
-        char16_t number;
+        char16_t number = 0;
 
         /* The list of key behaviors sorted from most specific to least specific
          * meta key binding. */
@@ -218,7 +215,6 @@
 
     public:
         Parser(KeyCharacterMap* map, Tokenizer* tokenizer, Format format);
-        ~Parser();
         status_t parse();
 
     private:
@@ -227,13 +223,13 @@
         status_t parseMapKey();
         status_t parseKey();
         status_t parseKeyProperty();
-        status_t finishKey(Key* key);
+        status_t finishKey(Key& key);
         status_t parseModifier(const std::string& token, int32_t* outMetaState);
         status_t parseCharacterLiteral(char16_t* outCharacter);
     };
 
-    KeyedVector<int32_t, Key*> mKeys;
-    KeyboardType mType;
+    std::map<int32_t, Key> mKeys;
+    KeyboardType mType = KeyboardType::UNKNOWN;
     std::string mLoadFileName;
     bool mLayoutOverlayApplied = false;
 
@@ -243,7 +239,7 @@
 
     KeyCharacterMap(const std::string& filename);
 
-    bool getKey(int32_t keyCode, const Key** outKey) const;
+    const Key* getKey(int32_t keyCode) const;
     const Behavior* getKeyBehavior(int32_t keyCode, int32_t metaState) const;
     static bool matchesMetaState(int32_t eventMetaState, int32_t behaviorMetaState);
 
diff --git a/include/input/KeyLayoutMap.h b/include/input/KeyLayoutMap.h
index e203d19..8c3c74a 100644
--- a/include/input/KeyLayoutMap.h
+++ b/include/input/KeyLayoutMap.h
@@ -72,6 +72,7 @@
             int32_t* outKeyCode, uint32_t* outFlags) const;
     std::vector<int32_t> findScanCodesForKey(int32_t keyCode) const;
     std::optional<int32_t> findScanCodeForLed(int32_t ledCode) const;
+    std::vector<int32_t> findUsageCodesForKey(int32_t keyCode) const;
     std::optional<int32_t> findUsageCodeForLed(int32_t ledCode) const;
 
     std::optional<AxisInfo> mapAxis(int32_t scanCode) const;
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/RingBuffer.h b/include/input/RingBuffer.h
index 67984b7..37fe5af 100644
--- a/include/input/RingBuffer.h
+++ b/include/input/RingBuffer.h
@@ -103,6 +103,11 @@
     iterator end() { return {*this, mSize}; }
     const_iterator end() const { return {*this, mSize}; }
 
+    reference front() { return mBuffer[mBegin]; }
+    const_reference front() const { return mBuffer[mBegin]; }
+    reference back() { return mBuffer[bufferIndex(mSize - 1)]; }
+    const_reference back() const { return mBuffer[bufferIndex(mSize - 1)]; }
+
     reference operator[](size_type i) { return mBuffer[bufferIndex(i)]; }
     const_reference operator[](size_type i) const { return mBuffer[bufferIndex(i)]; }
 
diff --git a/include/input/TfLiteMotionPredictor.h b/include/input/TfLiteMotionPredictor.h
index 7de551b41..a340bd0 100644
--- a/include/input/TfLiteMotionPredictor.h
+++ b/include/input/TfLiteMotionPredictor.h
@@ -106,6 +106,9 @@
     // Returns the length of the model's input buffers.
     size_t inputLength() const;
 
+    // Returns the length of the model's output buffers.
+    size_t outputLength() const;
+
     // Executes the model.
     // Returns true if the model successfully executed and the output tensors can be read.
     bool invoke();
diff --git a/include/input/VirtualInputDevice.h b/include/input/VirtualInputDevice.h
index 13ffb58..21a2877 100644
--- a/include/input/VirtualInputDevice.h
+++ b/include/input/VirtualInputDevice.h
@@ -34,10 +34,12 @@
 
 protected:
     const android::base::unique_fd mFd;
-    bool writeInputEvent(uint16_t type, uint16_t code, int32_t value);
+    bool writeInputEvent(uint16_t type, uint16_t code, int32_t value,
+                         std::chrono::nanoseconds eventTime);
     bool writeEvKeyEvent(int32_t androidCode, int32_t androidAction,
                          const std::map<int, int>& evKeyCodeMapping,
-                         const std::map<int, UinputAction>& actionMapping);
+                         const std::map<int, UinputAction>& actionMapping,
+                         std::chrono::nanoseconds eventTime);
 };
 
 class VirtualKeyboard : public VirtualInputDevice {
@@ -47,7 +49,8 @@
     static const std::map<int, UinputAction> KEY_ACTION_MAPPING;
     VirtualKeyboard(android::base::unique_fd fd);
     virtual ~VirtualKeyboard() override;
-    bool writeKeyEvent(int32_t androidKeyCode, int32_t androidAction);
+    bool writeKeyEvent(int32_t androidKeyCode, int32_t androidAction,
+                       std::chrono::nanoseconds eventTime);
 };
 
 class VirtualDpad : public VirtualInputDevice {
@@ -55,17 +58,20 @@
     static const std::map<int, int> DPAD_KEY_CODE_MAPPING;
     VirtualDpad(android::base::unique_fd fd);
     virtual ~VirtualDpad() override;
-    bool writeDpadKeyEvent(int32_t androidKeyCode, int32_t androidAction);
+    bool writeDpadKeyEvent(int32_t androidKeyCode, int32_t androidAction,
+                           std::chrono::nanoseconds eventTime);
 };
 
 class VirtualMouse : public VirtualInputDevice {
 public:
     VirtualMouse(android::base::unique_fd fd);
     virtual ~VirtualMouse() override;
-    bool writeButtonEvent(int32_t androidButtonCode, int32_t androidAction);
+    bool writeButtonEvent(int32_t androidButtonCode, int32_t androidAction,
+                          std::chrono::nanoseconds eventTime);
     // TODO(b/259554911): changing float parameters to int32_t.
-    bool writeRelativeEvent(float relativeX, float relativeY);
-    bool writeScrollEvent(float xAxisMovement, float yAxisMovement);
+    bool writeRelativeEvent(float relativeX, float relativeY, std::chrono::nanoseconds eventTime);
+    bool writeScrollEvent(float xAxisMovement, float yAxisMovement,
+                          std::chrono::nanoseconds eventTime);
 
 private:
     static const std::map<int, UinputAction> BUTTON_ACTION_MAPPING;
@@ -78,7 +84,8 @@
     virtual ~VirtualTouchscreen() override;
     // TODO(b/259554911): changing float parameters to int32_t.
     bool writeTouchEvent(int32_t pointerId, int32_t toolType, int32_t action, float locationX,
-                         float locationY, float pressure, float majorAxisSize);
+                         float locationY, float pressure, float majorAxisSize,
+                         std::chrono::nanoseconds eventTime);
 
 private:
     static const std::map<int, UinputAction> TOUCH_ACTION_MAPPING;
@@ -91,7 +98,7 @@
      */
     std::bitset<MAX_POINTERS> mActivePointers{};
     bool isValidPointerId(int32_t pointerId, UinputAction uinputAction);
-    bool handleTouchDown(int32_t pointerId);
-    bool handleTouchUp(int32_t pointerId);
+    bool handleTouchDown(int32_t pointerId, std::chrono::nanoseconds eventTime);
+    bool handleTouchUp(int32_t pointerId, std::chrono::nanoseconds eventTime);
 };
 } // namespace android
diff --git a/libs/binder/ActivityManager.cpp b/libs/binder/ActivityManager.cpp
index e45a656..aca5009 100644
--- a/libs/binder/ActivityManager.cpp
+++ b/libs/binder/ActivityManager.cpp
@@ -75,6 +75,20 @@
     return DEAD_OBJECT;
 }
 
+status_t ActivityManager::registerUidObserverForUids(const sp<IUidObserver>& observer,
+                                                     const int32_t event, const int32_t cutpoint,
+                                                     const String16& callingPackage,
+                                                     const int32_t uids[], size_t nUids,
+                                                     /*out*/ sp<IBinder>& observerToken) {
+    sp<IActivityManager> service = getService();
+    if (service != nullptr) {
+        return service->registerUidObserverForUids(observer, event, cutpoint, callingPackage, uids,
+                                                   nUids, observerToken);
+    }
+    // ActivityManagerService appears dead. Return usual error code for dead service.
+    return DEAD_OBJECT;
+}
+
 status_t ActivityManager::unregisterUidObserver(const sp<IUidObserver>& observer)
 {
     sp<IActivityManager> service = getService();
@@ -85,6 +99,26 @@
     return DEAD_OBJECT;
 }
 
+status_t ActivityManager::addUidToObserver(const sp<IBinder>& observerToken,
+                                           const String16& callingPackage, int32_t uid) {
+    sp<IActivityManager> service = getService();
+    if (service != nullptr) {
+        return service->addUidToObserver(observerToken, callingPackage, uid);
+    }
+    // ActivityManagerService appears dead. Return usual error code for dead service.
+    return DEAD_OBJECT;
+}
+
+status_t ActivityManager::removeUidFromObserver(const sp<IBinder>& observerToken,
+                                                const String16& callingPackage, int32_t uid) {
+    sp<IActivityManager> service = getService();
+    if (service != nullptr) {
+        return service->removeUidFromObserver(observerToken, callingPackage, uid);
+    }
+    // ActivityManagerService appears dead. Return usual error code for dead service.
+    return DEAD_OBJECT;
+}
+
 bool ActivityManager::isUidActive(const uid_t uid, const String16& callingPackage)
 {
     sp<IActivityManager> service = getService();
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index 6a354b4..49dd9c7 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*",
     ],
 }
@@ -354,6 +355,7 @@
 
 cc_library_static {
     name: "libbinder_rpc_no_kernel",
+    vendor_available: true,
     defaults: [
         "libbinder_common_defaults",
         "libbinder_android_defaults",
@@ -406,6 +408,7 @@
 cc_defaults {
     name: "libbinder_tls_defaults",
     defaults: ["libbinder_tls_shared_deps"],
+    vendor_available: true,
     host_supported: true,
 
     header_libs: [
@@ -432,7 +435,7 @@
     defaults: ["libbinder_tls_defaults"],
 }
 
-cc_library_shared {
+cc_library {
     name: "libbinder_trusty",
     vendor: true,
     srcs: [
diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp
index 53852d8..8d9955d 100644
--- a/libs/binder/BpBinder.cpp
+++ b/libs/binder/BpBinder.cpp
@@ -47,7 +47,7 @@
 binder_proxy_limit_callback BpBinder::sLimitCallback;
 bool BpBinder::sBinderProxyThrottleCreate = false;
 
-static StaticString16 kDescriptorUninit(u"<uninit descriptor>");
+static StaticString16 kDescriptorUninit(u"");
 
 // Arbitrarily high value that probably distinguishes a bad behaving app
 uint32_t BpBinder::sBinderProxyCountHighWatermark = 2500;
diff --git a/libs/binder/IActivityManager.cpp b/libs/binder/IActivityManager.cpp
index 08169f5..f2b4a6e 100644
--- a/libs/binder/IActivityManager.cpp
+++ b/libs/binder/IActivityManager.cpp
@@ -77,6 +77,30 @@
          return OK;
     }
 
+    virtual status_t registerUidObserverForUids(const sp<IUidObserver>& observer,
+                                                const int32_t event, const int32_t cutpoint,
+                                                const String16& callingPackage,
+                                                const int32_t uids[], size_t nUids,
+                                                /*out*/ sp<IBinder>& observerToken) {
+         Parcel data, reply;
+         data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor());
+         data.writeStrongBinder(IInterface::asBinder(observer));
+         data.writeInt32(event);
+         data.writeInt32(cutpoint);
+         data.writeString16(callingPackage);
+         data.writeInt32Array(nUids, uids);
+         status_t err =
+                 remote()->transact(REGISTER_UID_OBSERVER_FOR_UIDS_TRANSACTION, data, &reply);
+         if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
+             return err;
+         }
+         err = reply.readStrongBinder(&observerToken);
+         if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
+             return err;
+         }
+         return OK;
+    }
+
     virtual status_t unregisterUidObserver(const sp<IUidObserver>& observer)
     {
          Parcel data, reply;
@@ -89,6 +113,34 @@
          return OK;
     }
 
+    virtual status_t addUidToObserver(const sp<IBinder>& observerToken,
+                                      const String16& callingPackage, int32_t uid) {
+         Parcel data, reply;
+         data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor());
+         data.writeStrongBinder(observerToken);
+         data.writeString16(callingPackage);
+         data.writeInt32(uid);
+         status_t err = remote()->transact(ADD_UID_TO_OBSERVER_TRANSACTION, data, &reply);
+         if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
+             return err;
+         }
+         return OK;
+    }
+
+    virtual status_t removeUidFromObserver(const sp<IBinder>& observerToken,
+                                           const String16& callingPackage, int32_t uid) {
+         Parcel data, reply;
+         data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor());
+         data.writeStrongBinder(observerToken);
+         data.writeString16(callingPackage);
+         data.writeInt32(uid);
+         status_t err = remote()->transact(REMOVE_UID_FROM_OBSERVER_TRANSACTION, data, &reply);
+         if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
+             return err;
+         }
+         return OK;
+    }
+
     virtual bool isUidActive(const uid_t uid, const String16& callingPackage)
     {
          Parcel data, reply;
@@ -131,6 +183,56 @@
         *outResult = reply.readInt32();
         return NO_ERROR;
     }
+
+    virtual status_t logFgsApiBegin(int32_t apiType, int32_t appUid, int32_t appPid) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor());
+        data.writeInt32(apiType);
+        data.writeInt32(appUid);
+        data.writeInt32(appPid);
+        status_t err = remote()->transact(LOG_FGS_API_BEGIN_TRANSACTION, data, &reply,
+                                          IBinder::FLAG_ONEWAY);
+        if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
+            ALOGD("FGS Logger Transaction failed");
+            ALOGD("%d", err);
+            return err;
+        }
+        return NO_ERROR;
+    }
+
+    virtual status_t logFgsApiEnd(int32_t apiType, int32_t appUid, int32_t appPid) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor());
+        data.writeInt32(apiType);
+        data.writeInt32(appUid);
+        data.writeInt32(appPid);
+        status_t err =
+                remote()->transact(LOG_FGS_API_END_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY);
+        if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
+            ALOGD("FGS Logger Transaction failed");
+            ALOGD("%d", err);
+            return err;
+        }
+        return NO_ERROR;
+    }
+
+    virtual status_t logFgsApiStateChanged(int32_t apiType, int32_t state, int32_t appUid,
+                                           int32_t appPid) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor());
+        data.writeInt32(apiType);
+        data.writeInt32(state);
+        data.writeInt32(appUid);
+        data.writeInt32(appPid);
+        status_t err = remote()->transact(LOG_FGS_API_BEGIN_TRANSACTION, data, &reply,
+                                          IBinder::FLAG_ONEWAY);
+        if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
+            ALOGD("FGS Logger Transaction failed");
+            ALOGD("%d", err);
+            return err;
+        }
+        return NO_ERROR;
+    }
 };
 
 // ------------------------------------------------------------------------------------
diff --git a/libs/binder/IBatteryStats.cpp b/libs/binder/IBatteryStats.cpp
index 0de804c..69b11c0 100644
--- a/libs/binder/IBatteryStats.cpp
+++ b/libs/binder/IBatteryStats.cpp
@@ -128,6 +128,15 @@
         remote()->transact(NOTE_RESET_FLASHLIGHT_TRANSACTION, data, &reply);
     }
 
+    virtual binder::Status noteWakeupSensorEvent(int64_t elapsedNanos, int uid, int handle) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor());
+        data.writeInt64(elapsedNanos);
+        data.writeInt32(uid);
+        data.writeInt32(handle);
+        status_t ret = remote()->transact(NOTE_WAKEUP_SENSOR_EVENT_TRANSACTION, data, &reply);
+        return binder::Status::fromStatusT(ret);
+    }
 };
 
 IMPLEMENT_META_INTERFACE(BatteryStats, "com.android.internal.app.IBatteryStats")
@@ -235,6 +244,16 @@
             reply->writeNoException();
             return NO_ERROR;
         } break;
+        case NOTE_WAKEUP_SENSOR_EVENT_TRANSACTION: {
+            CHECK_INTERFACE(IBatteryStats, data, reply);
+            int64_t elapsedNanos = data.readInt64();
+            int uid = data.readInt32();
+            int handle = data.readInt32();
+            noteWakeupSensorEvent(elapsedNanos, uid, handle);
+            reply->writeNoException();
+            return NO_ERROR;
+        } break;
+
         default:
             return BBinder::onTransact(code, data, reply, flags);
     }
diff --git a/libs/binder/IUidObserver.cpp b/libs/binder/IUidObserver.cpp
index d952dc7..1c35f53 100644
--- a/libs/binder/IUidObserver.cpp
+++ b/libs/binder/IUidObserver.cpp
@@ -67,9 +67,10 @@
         remote()->transact(ON_UID_STATE_CHANGED_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY);
     }
 
-    virtual void onUidProcAdjChanged(uid_t uid) {
+    virtual void onUidProcAdjChanged(uid_t uid, int32_t adj) {
         Parcel data, reply;
         data.writeInt32((int32_t)uid);
+        data.writeInt32((int32_t)adj);
         remote()->transact(ON_UID_PROC_ADJ_CHANGED_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY);
     }
 };
@@ -121,7 +122,8 @@
         case ON_UID_PROC_ADJ_CHANGED_TRANSACTION: {
             CHECK_INTERFACE(IUidObserver, data, reply);
             uid_t uid = data.readInt32();
-            onUidProcAdjChanged(uid);
+            int32_t adj = data.readInt32();
+            onUidProcAdjChanged(uid, adj);
             return NO_ERROR;
         } break;
 
diff --git a/libs/binder/RecordedTransaction.cpp b/libs/binder/RecordedTransaction.cpp
index 51b97165..1c76135 100644
--- a/libs/binder/RecordedTransaction.cpp
+++ b/libs/binder/RecordedTransaction.cpp
@@ -127,17 +127,16 @@
     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;
     }
 
-    if (t.mSent.setData(dataParcel.data(), dataParcel.dataSize()) != android::NO_ERROR) {
+    if (t.mSent.setData(dataParcel.data(), dataParcel.dataBufferSize()) != android::NO_ERROR) {
         LOG(ERROR) << "Failed to set sent parcel data.";
         return std::nullopt;
     }
 
-    if (t.mReply.setData(replyParcel.data(), replyParcel.dataSize()) != android::NO_ERROR) {
+    if (t.mReply.setData(replyParcel.data(), replyParcel.dataBufferSize()) != android::NO_ERROR) {
         LOG(ERROR) << "Failed to set reply parcel data.";
         return std::nullopt;
     }
@@ -350,11 +349,11 @@
         return UNKNOWN_ERROR;
     }
 
-    if (NO_ERROR != writeChunk(fd, DATA_PARCEL_CHUNK, mSent.dataSize(), mSent.data())) {
+    if (NO_ERROR != writeChunk(fd, DATA_PARCEL_CHUNK, mSent.dataBufferSize(), mSent.data())) {
         LOG(ERROR) << "Failed to write sent Parcel to fd " << fd.get();
         return UNKNOWN_ERROR;
     }
-    if (NO_ERROR != writeChunk(fd, REPLY_PARCEL_CHUNK, mReply.dataSize(), mReply.data())) {
+    if (NO_ERROR != writeChunk(fd, REPLY_PARCEL_CHUNK, mReply.dataBufferSize(), mReply.data())) {
         LOG(ERROR) << "Failed to write reply Parcel to fd " << fd.get();
         return UNKNOWN_ERROR;
     }
diff --git a/libs/binder/RpcServer.cpp b/libs/binder/RpcServer.cpp
index 0d06e9e..9282856 100644
--- a/libs/binder/RpcServer.cpp
+++ b/libs/binder/RpcServer.cpp
@@ -295,7 +295,8 @@
 bool RpcServer::shutdown() {
     RpcMutexUniqueLock _l(mLock);
     if (mShutdownTrigger == nullptr) {
-        LOG_RPC_DETAIL("Cannot shutdown. No shutdown trigger installed (already shutdown?)");
+        LOG_RPC_DETAIL("Cannot shutdown. No shutdown trigger installed (already shutdown, or not "
+                       "joined yet?)");
         return false;
     }
 
@@ -552,7 +553,7 @@
             socket(addr.addr()->sa_family, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)));
     if (!socket_fd.ok()) {
         int savedErrno = errno;
-        ALOGE("Could not create socket: %s", strerror(savedErrno));
+        ALOGE("Could not create socket at %s: %s", addr.toString().c_str(), strerror(savedErrno));
         return -savedErrno;
     }
     if (0 != TEMP_FAILURE_RETRY(bind(socket_fd.get(), addr.addr(), addr.addrSize()))) {
diff --git a/libs/binder/RpcSession.cpp b/libs/binder/RpcSession.cpp
index 233a8e4..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>
 
@@ -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()))) {
diff --git a/libs/binder/RpcState.cpp b/libs/binder/RpcState.cpp
index 2b0e5ba..03fa699 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
 }
 
@@ -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 = {
@@ -925,7 +928,7 @@
                                           transactionData.size() -
                                                   offsetof(RpcWireTransaction, data)};
         Span<const uint32_t> objectTableSpan;
-        if (session->getProtocolVersion().value() >
+        if (session->getProtocolVersion().value() >=
             RPC_WIRE_PROTOCOL_VERSION_RPC_HEADER_FEATURE_EXPLICIT_PARCEL_SIZE) {
             std::optional<Span<const uint8_t>> objectTableBytes =
                     parcelSpan.splitOff(transaction->parcelDataSize);
@@ -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/RpcTransportTipcAndroid.cpp b/libs/binder/RpcTransportTipcAndroid.cpp
index 8b3ddfb..d5a6da2 100644
--- a/libs/binder/RpcTransportTipcAndroid.cpp
+++ b/libs/binder/RpcTransportTipcAndroid.cpp
@@ -54,7 +54,7 @@
             }
 
             LOG_RPC_DETAIL("RpcTransport poll(): %s", strerror(savedErrno));
-            return -savedErrno;
+            return adjustStatus(-savedErrno);
         }
 
         if (pfd.revents & POLLNVAL) {
@@ -87,8 +87,10 @@
                                 "File descriptors are not supported on Trusty yet");
             return TEMP_FAILURE_RETRY(tipc_send(mSocket.fd.get(), iovs, niovs, nullptr, 0));
         };
-        return interruptableReadOrWrite(mSocket, fdTrigger, iovs, niovs, writeFn, "tipc_send",
-                                        POLLOUT, altPoll);
+
+        status_t status = interruptableReadOrWrite(mSocket, fdTrigger, iovs, niovs, writeFn,
+                                                   "tipc_send", POLLOUT, altPoll);
+        return adjustStatus(status);
     }
 
     status_t interruptableReadFully(
@@ -121,13 +123,26 @@
 
             return processSize;
         };
-        return interruptableReadOrWrite(mSocket, fdTrigger, iovs, niovs, readFn, "read", POLLIN,
-                                        altPoll);
+
+        status_t status = interruptableReadOrWrite(mSocket, fdTrigger, iovs, niovs, readFn, "read",
+                                                   POLLIN, altPoll);
+        return adjustStatus(status);
     }
 
     bool isWaiting() override { return mSocket.isInPollingState(); }
 
 private:
+    status_t adjustStatus(status_t status) {
+        if (status == -ENOTCONN) {
+            // TIPC returns ENOTCONN on disconnect, but that's basically
+            // the same as DEAD_OBJECT and the latter is the common libbinder
+            // error code for dead connections
+            return DEAD_OBJECT;
+        }
+
+        return status;
+    }
+
     status_t fillReadBuffer() {
         if (mReadBufferPos < mReadBufferSize) {
             return OK;
@@ -167,7 +182,7 @@
                     continue;
                 } else {
                     LOG_RPC_DETAIL("RpcTransport fillBuffer(): %s", strerror(savedErrno));
-                    return -savedErrno;
+                    return adjustStatus(-savedErrno);
                 }
             } else {
                 mReadBufferSize = static_cast<size_t>(processSize);
diff --git a/libs/binder/TEST_MAPPING b/libs/binder/TEST_MAPPING
index 1488400..0e8e187 100644
--- a/libs/binder/TEST_MAPPING
+++ b/libs/binder/TEST_MAPPING
@@ -79,6 +79,12 @@
     },
     {
       "name": "rustBinderSerializationTest"
+    },
+    {
+      "name": "libbinder_ndk_bindgen_test"
+    },
+    {
+      "name": "libbinder_rpc_unstable_bindgen_test"
     }
   ],
   "presubmit-large": [
@@ -86,12 +92,6 @@
       "name": "binderRpcTest"
     },
     {
-      "name": "CtsRootRollbackManagerHostTestCases"
-    },
-    {
-      "name": "StagedRollbackTest"
-    },
-    {
       "name": "binderRpcTestNoKernel"
     },
     {
@@ -119,5 +119,10 @@
     {
       "name": "memunreachable_binder_test"
     }
+  ],
+  "imports": [
+    {
+      "path": "packages/modules/Virtualization"
+    }
   ]
 }
diff --git a/libs/binder/include/binder/RpcSession.h b/libs/binder/include/binder/RpcSession.h
index 0750ccf..cb64603 100644
--- a/libs/binder/include/binder/RpcSession.h
+++ b/libs/binder/include/binder/RpcSession.h
@@ -37,9 +37,9 @@
 class RpcTransport;
 class FdTrigger;
 
-constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION_NEXT = 1;
+constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION_NEXT = 2;
 constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL = 0xF0000000;
-constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION = RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL;
+constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION = 1;
 
 // Starting with this version:
 //
@@ -51,6 +51,9 @@
  * 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:
diff --git a/libs/binder/include_activitymanager/binder/ActivityManager.h b/libs/binder/include_activitymanager/binder/ActivityManager.h
index 5dfbd44..9c634c7 100644
--- a/libs/binder/include_activitymanager/binder/ActivityManager.h
+++ b/libs/binder/include_activitymanager/binder/ActivityManager.h
@@ -82,7 +82,15 @@
                              const int32_t event,
                              const int32_t cutpoint,
                              const String16& callingPackage);
+    status_t registerUidObserverForUids(const sp<IUidObserver>& observer, const int32_t event,
+                                        const int32_t cutpoint, const String16& callingPackage,
+                                        const int32_t uids[], size_t nUids,
+                                        /*out*/ sp<IBinder>& observerToken);
     status_t unregisterUidObserver(const sp<IUidObserver>& observer);
+    status_t addUidToObserver(const sp<IBinder>& observerToken, const String16& callingPackage,
+                              int32_t uid);
+    status_t removeUidFromObserver(const sp<IBinder>& observerToken, const String16& callingPackage,
+                                   int32_t uid);
     bool isUidActive(const uid_t uid, const String16& callingPackage);
     int getUidProcessState(const uid_t uid, const String16& callingPackage);
     status_t checkPermission(const String16& permission, const pid_t pid, const uid_t uid, int32_t* outResult);
diff --git a/libs/binder/include_activitymanager/binder/IActivityManager.h b/libs/binder/include_activitymanager/binder/IActivityManager.h
index 4632b2e..07450c6 100644
--- a/libs/binder/include_activitymanager/binder/IActivityManager.h
+++ b/libs/binder/include_activitymanager/binder/IActivityManager.h
@@ -35,21 +35,40 @@
                                      const int32_t event,
                                      const int32_t cutpoint,
                                      const String16& callingPackage) = 0;
+    virtual status_t registerUidObserverForUids(const sp<IUidObserver>& observer,
+                                                const int32_t event, const int32_t cutpoint,
+                                                const String16& callingPackage,
+                                                const int32_t uids[], size_t nUids,
+                                                /*out*/ sp<IBinder>& observerToken) = 0;
     virtual status_t unregisterUidObserver(const sp<IUidObserver>& observer) = 0;
+    virtual status_t addUidToObserver(const sp<IBinder>& observerToken,
+                                      const String16& callingPackage, int32_t uid) = 0;
+    virtual status_t removeUidFromObserver(const sp<IBinder>& observerToken,
+                                           const String16& callingPackage, int32_t uid) = 0;
     virtual bool isUidActive(const uid_t uid, const String16& callingPackage) = 0;
     virtual int32_t getUidProcessState(const uid_t uid, const String16& callingPackage) = 0;
     virtual status_t checkPermission(const String16& permission,
                                     const pid_t pid,
                                     const uid_t uid,
                                     int32_t* outResult) = 0;
+    virtual status_t logFgsApiBegin(int32_t apiType, int32_t appUid, int32_t appPid) = 0;
+    virtual status_t logFgsApiEnd(int32_t apiType, int32_t appUid, int32_t appPid) = 0;
+    virtual status_t logFgsApiStateChanged(int32_t apiType, int32_t state, int32_t appUid,
+                                           int32_t appPid) = 0;
 
     enum {
         OPEN_CONTENT_URI_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
         REGISTER_UID_OBSERVER_TRANSACTION,
         UNREGISTER_UID_OBSERVER_TRANSACTION,
+        REGISTER_UID_OBSERVER_FOR_UIDS_TRANSACTION,
+        ADD_UID_TO_OBSERVER_TRANSACTION,
+        REMOVE_UID_FROM_OBSERVER_TRANSACTION,
         IS_UID_ACTIVE_TRANSACTION,
         GET_UID_PROCESS_STATE_TRANSACTION,
         CHECK_PERMISSION_TRANSACTION,
+        LOG_FGS_API_BEGIN_TRANSACTION,
+        LOG_FGS_API_END_TRANSACTION,
+        LOG_FGS_API_STATE_CHANGED_TRANSACTION
     };
 };
 
diff --git a/libs/binder/include_activitymanager/binder/IUidObserver.h b/libs/binder/include_activitymanager/binder/IUidObserver.h
index 17f03a9..5ea7447 100644
--- a/libs/binder/include_activitymanager/binder/IUidObserver.h
+++ b/libs/binder/include_activitymanager/binder/IUidObserver.h
@@ -34,7 +34,7 @@
     virtual void onUidIdle(uid_t uid, bool disabled) = 0;
     virtual void onUidStateChanged(uid_t uid, int32_t procState, int64_t procStateSeq,
                                    int32_t capability) = 0;
-    virtual void onUidProcAdjChanged(uid_t uid) = 0;
+    virtual void onUidProcAdjChanged(uid_t uid, int32_t adj) = 0;
 
     enum {
         ON_UID_GONE_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
diff --git a/libs/binder/include_batterystats/batterystats/IBatteryStats.h b/libs/binder/include_batterystats/batterystats/IBatteryStats.h
index 6defc7f..5bb01dd 100644
--- a/libs/binder/include_batterystats/batterystats/IBatteryStats.h
+++ b/libs/binder/include_batterystats/batterystats/IBatteryStats.h
@@ -19,6 +19,7 @@
 #ifndef __ANDROID_VNDK__
 
 #include <binder/IInterface.h>
+#include <binder/Status.h>
 
 namespace android {
 
@@ -43,6 +44,7 @@
     virtual void noteStopCamera(int uid) = 0;
     virtual void noteResetCamera() = 0;
     virtual void noteResetFlashlight() = 0;
+    virtual binder::Status noteWakeupSensorEvent(int64_t elapsedNanos, int uid, int sensor) = 0;
 
     enum {
         NOTE_START_SENSOR_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
@@ -58,7 +60,8 @@
         NOTE_START_CAMERA_TRANSACTION,
         NOTE_STOP_CAMERA_TRANSACTION,
         NOTE_RESET_CAMERA_TRANSACTION,
-        NOTE_RESET_FLASHLIGHT_TRANSACTION
+        NOTE_RESET_FLASHLIGHT_TRANSACTION,
+        NOTE_WAKEUP_SENSOR_EVENT_TRANSACTION
     };
 };
 
diff --git a/libs/binder/ndk/include_platform/android/binder_manager.h b/libs/binder/ndk/include_platform/android/binder_manager.h
index 43159d8..89fd7a3 100644
--- a/libs/binder/ndk/include_platform/android/binder_manager.h
+++ b/libs/binder/ndk/include_platform/android/binder_manager.h
@@ -56,12 +56,12 @@
  *
  * \param binder object to register globally with the service manager.
  * \param instance identifier of the service. This will be used to lookup the service.
- * \param flag an AServiceManager_AddServiceFlag enum to denote how the service should be added.
+ * \param flags an AServiceManager_AddServiceFlag enum to denote how the service should be added.
  *
  * \return EX_NONE on success.
  */
-__attribute__((warn_unused_result)) binder_exception_t AServiceManager_addServiceWithFlag(
-        AIBinder* binder, const char* instance, const AServiceManager_AddServiceFlag flag)
+__attribute__((warn_unused_result)) binder_exception_t AServiceManager_addServiceWithFlags(
+        AIBinder* binder, const char* instance, const AServiceManager_AddServiceFlag flags)
         __INTRODUCED_IN(34);
 
 /**
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/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index 1078fb2..1c5f79f 100644
--- a/libs/binder/ndk/libbinder_ndk.map.txt
+++ b/libs/binder/ndk/libbinder_ndk.map.txt
@@ -158,7 +158,7 @@
     AServiceManager_getUpdatableApexName; # systemapi
     AServiceManager_registerForServiceNotifications; # systemapi llndk
     AServiceManager_NotificationRegistration_delete; # systemapi llndk
-    AServiceManager_addServiceWithFlag; # systemapi llndk
+    AServiceManager_addServiceWithFlags; # systemapi llndk
 };
 
 LIBBINDER_NDK_PLATFORM {
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/service_manager.cpp b/libs/binder/ndk/service_manager.cpp
index 84da459..2977786 100644
--- a/libs/binder/ndk/service_manager.cpp
+++ b/libs/binder/ndk/service_manager.cpp
@@ -42,15 +42,15 @@
     return PruneException(exception);
 }
 
-binder_exception_t AServiceManager_addServiceWithFlag(AIBinder* binder, const char* instance,
-                                                      const AServiceManager_AddServiceFlag flag) {
+binder_exception_t AServiceManager_addServiceWithFlags(AIBinder* binder, const char* instance,
+                                                       const AServiceManager_AddServiceFlag flags) {
     if (binder == nullptr || instance == nullptr) {
         return EX_ILLEGAL_ARGUMENT;
     }
 
     sp<IServiceManager> sm = defaultServiceManager();
 
-    bool allowIsolated = flag & AServiceManager_AddServiceFlag::ADD_SERVICE_ALLOW_ISOLATED;
+    bool allowIsolated = flags & AServiceManager_AddServiceFlag::ADD_SERVICE_ALLOW_ISOLATED;
     status_t exception = sm->addService(String16(instance), binder->getBinder(), allowIsolated);
     return PruneException(exception);
 }
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index 882f1d6..cefc42f 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -51,6 +51,7 @@
 constexpr char kLazyBinderNdkUnitTestService[] = "LazyBinderNdkUnitTest";
 constexpr char kForcePersistNdkUnitTestService[] = "ForcePersistNdkUnitTestService";
 constexpr char kActiveServicesNdkUnitTestService[] = "ActiveServicesNdkUnitTestService";
+constexpr char kBinderNdkUnitTestServiceFlagged[] = "BinderNdkUnitTestFlagged";
 
 constexpr unsigned int kShutdownWaitTime = 11;
 constexpr uint64_t kContextTestValue = 0xb4e42fb4d9a1d715;
@@ -158,6 +159,24 @@
     return 1;  // should not return
 }
 
+int generatedFlaggedService(const AServiceManager_AddServiceFlag flags, const char* instance) {
+    ABinderProcess_setThreadPoolMaxThreadCount(0);
+
+    auto service = ndk::SharedRefBase::make<MyBinderNdkUnitTest>();
+    auto binder = service->asBinder();
+
+    binder_exception_t exception =
+            AServiceManager_addServiceWithFlags(binder.get(), instance, flags);
+
+    if (exception != EX_NONE) {
+        LOG(FATAL) << "Could not register: " << exception << " " << instance;
+    }
+
+    ABinderProcess_joinThreadPool();
+
+    return 1;  // should not return
+}
+
 // manually-written parceling class considered bad practice
 class MyFoo : public IFoo {
     binder_status_t doubleNumber(int32_t in, int32_t* out) override {
@@ -847,6 +866,12 @@
     EXPECT_EQ("CMD", shellCmdToString(testService, {"C", "M", "D"}));
 }
 
+TEST(NdkBinder, FlaggedServiceAccessible) {
+    static const sp<android::IServiceManager> sm(android::defaultServiceManager());
+    sp<IBinder> testService = sm->getService(String16(kBinderNdkUnitTestServiceFlagged));
+    ASSERT_NE(nullptr, testService);
+}
+
 TEST(NdkBinder, GetClassInterfaceDescriptor) {
     ASSERT_STREQ(IFoo::kIFooDescriptor, AIBinder_Class_getDescriptor(IFoo::kClass));
 }
@@ -901,6 +926,13 @@
         prctl(PR_SET_PDEATHSIG, SIGHUP);
         return generatedService();
     }
+    if (fork() == 0) {
+        prctl(PR_SET_PDEATHSIG, SIGHUP);
+        // We may want to change this flag to be more generic ones for the future
+        AServiceManager_AddServiceFlag test_flags =
+                AServiceManager_AddServiceFlag::ADD_SERVICE_ALLOW_ISOLATED;
+        return generatedFlaggedService(test_flags, kBinderNdkUnitTestServiceFlagged);
+    }
 
     ABinderProcess_setThreadPoolMaxThreadCount(1);  // to receive death notifications/callbacks
     ABinderProcess_startThreadPool();
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/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/Android.bp b/libs/binder/tests/Android.bp
index 0f0d64a..873e955 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -138,6 +138,7 @@
 
 aidl_interface {
     name: "binderRpcTestIface",
+    vendor_available: true,
     host_supported: true,
     unstable: true,
     srcs: [
@@ -159,6 +160,7 @@
 cc_library_static {
     name: "libbinder_tls_test_utils",
     host_supported: true,
+    vendor_available: true,
     target: {
         darwin: {
             enabled: false,
@@ -236,6 +238,13 @@
         "binderRpcUniversalTests.cpp",
     ],
 
+    // This test uses a lot of resources and takes a long time. Due to
+    // design of several tests, it is also very sensitive to resource
+    // contention on the device. b/276820894
+    test_options: {
+        unit_test: false,
+    },
+
     test_suites: ["general-tests"],
     require_root: true,
 
@@ -441,6 +450,37 @@
 }
 
 cc_test {
+    name: "binderRpcToTrustyTest",
+    vendor: true,
+    host_supported: false,
+    defaults: [
+        "binderRpcTest_common_defaults",
+        "binderRpcTest_static_defaults",
+    ],
+
+    srcs: [
+        "binderRpcTest.cpp",
+        "binderRpcTestCommon.cpp",
+        "binderRpcUniversalTests.cpp",
+    ],
+
+    cflags: [
+        "-DBINDER_RPC_TO_TRUSTY_TEST",
+    ],
+
+    static_libs: [
+        // We want to link libbinder statically so we can push the binary
+        // to the device for testing independently of the library
+        "libbinder_rpc_no_kernel",
+        "libbinder_trusty",
+        "libtrusty",
+    ],
+
+    test_suites: ["device-tests"],
+    require_root: true,
+}
+
+cc_test {
     name: "RpcTlsUtilsTest",
     host_supported: true,
     target: {
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/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 6e34d25..8d13007 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -28,10 +28,10 @@
 #include <sys/prctl.h>
 #include <sys/socket.h>
 
-#ifdef __ANDROID_VENDOR__
+#ifdef BINDER_RPC_TO_TRUSTY_TEST
 #include <binder/RpcTransportTipcAndroid.h>
 #include <trusty/tipc.h>
-#endif // __ANDROID_VENDOR__
+#endif // BINDER_RPC_TO_TRUSTY_TEST
 
 #include "binderRpcTestCommon.h"
 #include "binderRpcTestFixture.h"
@@ -50,7 +50,7 @@
 constexpr bool kEnableSharedLibs = true;
 #endif
 
-#ifdef __ANDROID_VENDOR__
+#ifdef BINDER_RPC_TO_TRUSTY_TEST
 constexpr char kTrustyIpcDevice[] = "/dev/trusty-ipc-dev0";
 #endif
 
@@ -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;
@@ -212,6 +214,7 @@
     return serverFd;
 }
 
+#ifndef BINDER_RPC_TO_TRUSTY_TEST
 static base::unique_fd connectToUnixBootstrap(const RpcTransportFd& transportFd) {
     base::unique_fd sockClient, sockServer;
     if (!base::Socketpair(SOCK_STREAM, &sockClient, &sockServer)) {
@@ -230,18 +233,10 @@
     }
     return std::move(sockClient);
 }
+#endif // BINDER_RPC_TO_TRUSTY_TEST
 
-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 +245,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());
@@ -311,13 +310,13 @@
     for (size_t i = 0; i < options.numSessions; i++) {
         std::unique_ptr<RpcTransportCtxFactory> factory;
         if (socketType == SocketType::TIPC) {
-#ifdef __ANDROID_VENDOR__
+#ifdef BINDER_RPC_TO_TRUSTY_TEST
             factory = RpcTransportCtxFactoryTipcAndroid::make();
 #else
             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,9 +346,15 @@
 
     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->setMaxIncomingThreads(numIncoming);
         session->setMaxOutgoingConnections(options.numOutgoingConnections);
         session->setFileDescriptorTransportMode(options.clientFileDescriptorTransportMode);
 
@@ -375,11 +380,18 @@
                 break;
             case SocketType::TIPC:
                 status = session->setupPreconnectedClient({}, [=]() {
-#ifdef __ANDROID_VENDOR__
+#ifdef BINDER_RPC_TO_TRUSTY_TEST
                     auto port = trustyIpcPort(serverVersion);
-                    int tipcFd = tipc_connect(kTrustyIpcDevice, port.c_str());
-                    return tipcFd >= 0 ? android::base::unique_fd(tipcFd)
-                                       : android::base::unique_fd();
+                    for (size_t i = 0; i < 5; i++) {
+                        // Try to connect several times,
+                        // in case the service is slow to start
+                        int tipcFd = tipc_connect(kTrustyIpcDevice, port.c_str());
+                        if (tipcFd >= 0) {
+                            return android::base::unique_fd(tipcFd);
+                        }
+                        usleep(50000);
+                    }
+                    return android::base::unique_fd();
 #else
                     LOG_ALWAYS_FATAL("Tried to connect to Trusty outside of vendor");
                     return android::base::unique_fd();
@@ -435,8 +447,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 +473,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 +488,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 +498,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});
 
@@ -584,30 +599,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);
 }
@@ -656,6 +663,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";
@@ -673,7 +706,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));
@@ -686,6 +719,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;
 }
@@ -707,7 +744,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));
@@ -740,8 +777,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));
@@ -760,19 +796,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) {
@@ -1081,12 +1111,26 @@
     ASSERT_EQ(beforeFds, countFds()) << (system("ls -l /proc/self/fd/"), "fd leak?");
 }
 
+#ifdef BINDER_RPC_TO_TRUSTY_TEST
+INSTANTIATE_TEST_CASE_P(Trusty, BinderRpc,
+                        ::testing::Combine(::testing::Values(SocketType::TIPC),
+                                           ::testing::Values(RpcSecurity::RAW),
+                                           ::testing::ValuesIn(testVersions()),
+                                           ::testing::ValuesIn(testVersions()),
+                                           ::testing::Values(true), ::testing::Values(true)),
+                        BinderRpc::PrintParamInfo);
+#else // BINDER_RPC_TO_TRUSTY_TEST
 static bool testSupportVsockLoopback() {
     // We don't need to enable TLS to know if vsock is supported.
     unsigned int vsockPort = allocateVsockPort();
 
     android::base::unique_fd serverFd(
             TEMP_FAILURE_RETRY(socket(AF_VSOCK, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)));
+
+    if (errno == EAFNOSUPPORT) {
+        return false;
+    }
+
     LOG_ALWAYS_FATAL_IF(serverFd == -1, "Could not create socket: %s", strerror(errno));
 
     sockaddr_vm serverAddr{
@@ -1185,21 +1229,6 @@
     return ret;
 }
 
-static std::vector<SocketType> testTipcSocketTypes() {
-#ifdef __ANDROID_VENDOR__
-    auto port = trustyIpcPort(RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL);
-    int tipcFd = tipc_connect(kTrustyIpcDevice, port.c_str());
-    if (tipcFd >= 0) {
-        close(tipcFd);
-        return {SocketType::TIPC};
-    }
-#endif // __ANDROID_VENDOR__
-
-    // TIPC is not supported on this device, most likely
-    // because /dev/trusty-ipc-dev0 is missing
-    return {};
-}
-
 INSTANTIATE_TEST_CASE_P(PerSocket, BinderRpc,
                         ::testing::Combine(::testing::ValuesIn(testSocketTypes()),
                                            ::testing::ValuesIn(RpcSecurityValues()),
@@ -1209,14 +1238,6 @@
                                            ::testing::Values(false, true)),
                         BinderRpc::PrintParamInfo);
 
-INSTANTIATE_TEST_CASE_P(Trusty, BinderRpc,
-                        ::testing::Combine(::testing::ValuesIn(testTipcSocketTypes()),
-                                           ::testing::Values(RpcSecurity::RAW),
-                                           ::testing::ValuesIn(testVersions()),
-                                           ::testing::ValuesIn(testVersions()),
-                                           ::testing::Values(true), ::testing::Values(true)),
-                        BinderRpc::PrintParamInfo);
-
 class BinderRpcServerRootObject
       : public ::testing::TestWithParam<std::tuple<bool, bool, RpcSecurity>> {};
 
@@ -1227,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);
@@ -1323,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));
     }
 };
@@ -1331,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)));
@@ -1347,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>();
@@ -1396,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: {
@@ -1475,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();
@@ -1580,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();
         }
@@ -1652,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;
@@ -1985,6 +2006,7 @@
                          testing::Values(RpcKeyFormat::PEM, RpcKeyFormat::DER),
                          testing::ValuesIn(testVersions())),
         RpcTransportTlsKeyTest::PrintParamInfo);
+#endif // BINDER_RPC_TO_TRUSTY_TEST
 
 } // namespace android
 
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 84abbac..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,8 +65,7 @@
 
     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->setMaxOutgoingConnections(options.numOutgoingConnections);
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/binderRpcWireProtocolTest.cpp b/libs/binder/tests/binderRpcWireProtocolTest.cpp
index 3dab2c7..642cea4 100644
--- a/libs/binder/tests/binderRpcWireProtocolTest.cpp
+++ b/libs/binder/tests/binderRpcWireProtocolTest.cpp
@@ -237,14 +237,25 @@
     checkRepr(kCurrentRepr, 0);
 }
 
+TEST(RpcWire, V1) {
+    checkRepr(kCurrentRepr, 1);
+}
+
 TEST(RpcWire, CurrentVersion) {
     checkRepr(kCurrentRepr, RPC_WIRE_PROTOCOL_VERSION);
 }
 
-static_assert(RPC_WIRE_PROTOCOL_VERSION == RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL,
+static_assert(RPC_WIRE_PROTOCOL_VERSION == 1,
               "If the binder wire protocol is updated, this test should test additional versions. "
               "The binder wire protocol should only be updated on upstream AOSP.");
 
+TEST(RpcWire, NextIsPlusOneReminder) {
+    if (RPC_WIRE_PROTOCOL_VERSION != RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL) {
+        EXPECT_EQ(RPC_WIRE_PROTOCOL_VERSION + 1, RPC_WIRE_PROTOCOL_VERSION_NEXT)
+                << "Make sure to note what the next version should be.";
+    }
+}
+
 TEST(RpcWire, ReleaseBranchHasFrozenRpcWireProtocol) {
     if (RPC_WIRE_PROTOCOL_VERSION == RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL) {
         EXPECT_FALSE(base::GetProperty("ro.build.version.codename", "") == "REL")
diff --git a/libs/binder/tests/parcel_fuzzer/binder.cpp b/libs/binder/tests/parcel_fuzzer/binder.cpp
index 768fbe1..46d387c 100644
--- a/libs/binder/tests/parcel_fuzzer/binder.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder.cpp
@@ -367,13 +367,15 @@
         FUZZ_LOG() << "about to call readFromParcel() with status for SingleDataParcelable";
         parcelables::SingleDataParcelable singleDataParcelable;
         status_t status = singleDataParcelable.readFromParcel(&p);
-        FUZZ_LOG() <<" status: " << status;
+        FUZZ_LOG() << " status: " << status;
     },
     [] (const ::android::Parcel& p, FuzzedDataProvider& /*provider*/) {
         FUZZ_LOG() << "about to call readFromParcel() with status for GenericDataParcelable";
         parcelables::GenericDataParcelable genericDataParcelable;
         status_t status = genericDataParcelable.readFromParcel(&p);
-        FUZZ_LOG() <<" status: " << status;
+        FUZZ_LOG() << " status: " << status;
+        std::string toString = genericDataParcelable.toString();
+        FUZZ_LOG() << " toString() result: " << toString;
     },
 };
 // clang-format on
diff --git a/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp b/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
index 08eb27a..3a1471e 100644
--- a/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
@@ -198,6 +198,8 @@
             aidl::parcelables::GenericDataParcelable genericDataParcelable;
             binder_status_t status = genericDataParcelable.readFromParcel(p.aParcel());
             FUZZ_LOG() << "status: " << status;
+            std::string toString = genericDataParcelable.toString();
+            FUZZ_LOG() << "toString() result: " << toString;
         },
         [](const NdkParcelAdapter& p, FuzzedDataProvider& provider) {
             FUZZ_LOG() << "about to marshal AParcel";
diff --git a/libs/binder/tests/parcel_fuzzer/parcelables/GenericDataParcelable.aidl b/libs/binder/tests/parcel_fuzzer/parcelables/GenericDataParcelable.aidl
index f1079e9..dd08f72 100644
--- a/libs/binder/tests/parcel_fuzzer/parcelables/GenericDataParcelable.aidl
+++ b/libs/binder/tests/parcel_fuzzer/parcelables/GenericDataParcelable.aidl
@@ -16,10 +16,23 @@
 package parcelables;
 
 parcelable GenericDataParcelable {
+    enum JustSomeEnum {
+        SOME_ENUMERATOR,
+        ANOTHER_ENUMERATOR,
+        MAYBE_ONE_MORE_ENUMERATOR,
+    }
+
+    const int COOL_CONSTANT = 0x1234;
+
     int data;
     float majorVersion;
     float minorVersion;
     IBinder binder;
     ParcelFileDescriptor fileDescriptor;
     int[] array;
-}
\ No newline at end of file
+    String greatString;
+    @utf8InCpp
+    String greaterString;
+    @nullable String nullableString;
+    JustSomeEnum gretEnum = JustSomeEnum.ANOTHER_ENUMERATOR;
+}
diff --git a/libs/binder/tests/unit_fuzzers/RecordedTransactionFileFuzz.cpp b/libs/binder/tests/unit_fuzzers/RecordedTransactionFileFuzz.cpp
index 73790fa..e494366 100644
--- a/libs/binder/tests/unit_fuzzers/RecordedTransactionFileFuzz.cpp
+++ b/libs/binder/tests/unit_fuzzers/RecordedTransactionFileFuzz.cpp
@@ -26,7 +26,7 @@
     rewind(intermediateFile);
     int fileNumber = fileno(intermediateFile);
 
-    android::base::unique_fd fd(fileNumber);
+    android::base::unique_fd fd(dup(fileNumber));
 
     auto transaction = android::binder::debug::RecordedTransaction::fromFile(fd);
 
diff --git a/libs/binder/trusty/RpcServerTrusty.cpp b/libs/binder/trusty/RpcServerTrusty.cpp
index 109da75..68b0008 100644
--- a/libs/binder/trusty/RpcServerTrusty.cpp
+++ b/libs/binder/trusty/RpcServerTrusty.cpp
@@ -65,6 +65,10 @@
     mTipcPort.msg_queue_len = 6; // Three each way
     mTipcPort.priv = this;
 
+    // TODO(b/266741352): follow-up to prevent needing this in the future
+    // Trusty needs to be set to the latest stable version that is in prebuilts there.
+    mRpcServer->setProtocolVersion(0);
+
     if (mPortAcl) {
         // Initialize the array of pointers to uuids.
         // The pointers in mUuidPtrs should stay valid across moves of
@@ -154,8 +158,18 @@
     return NO_ERROR;
 }
 
-void RpcServerTrusty::handleDisconnect(const tipc_port* /*port*/, handle_t /*chan*/,
-                                       void* /*ctx*/) {}
+void RpcServerTrusty::handleDisconnect(const tipc_port* /*port*/, handle_t /*chan*/, void* ctx) {
+    auto* channelContext = reinterpret_cast<ChannelContext*>(ctx);
+    if (channelContext == nullptr) {
+        // Connections marked "incoming" (outgoing from the server's side)
+        // do not have a valid channel context because joinFn does not get
+        // called for them. We ignore them here.
+        return;
+    }
+
+    auto& session = channelContext->session;
+    (void)session->shutdownAndWait(false);
+}
 
 void RpcServerTrusty::handleChannelCleanup(void* ctx) {
     auto* channelContext = reinterpret_cast<ChannelContext*>(ctx);
diff --git a/libs/binder/trusty/binderRpcTest/rules.mk b/libs/binder/trusty/binderRpcTest/rules.mk
index ae39492..975f689 100644
--- a/libs/binder/trusty/binderRpcTest/rules.mk
+++ b/libs/binder/trusty/binderRpcTest/rules.mk
@@ -32,4 +32,8 @@
 	trusty/user/base/lib/googletest \
 	trusty/user/base/lib/libstdc++-trusty \
 
+# TEST_P tests from binderRpcUniversalTests.cpp don't get linked in
+# unless we pass in --whole-archive to the linker (b/275620340).
+MODULE_USE_WHOLE_ARCHIVE := true
+
 include make/trusted_app.mk
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/BinderDebug.cpp b/libs/binderdebug/BinderDebug.cpp
index d086b49..a8f2cbf 100644
--- a/libs/binderdebug/BinderDebug.cpp
+++ b/libs/binderdebug/BinderDebug.cpp
@@ -48,14 +48,12 @@
             return -errno;
         }
     }
-    static const std::regex kContextLine("^context (\\w+)$");
 
     bool isDesiredContext = false;
     std::string line;
-    std::smatch match;
     while (getline(ifs, line)) {
-        if (std::regex_search(line, match, kContextLine)) {
-            isDesiredContext = match.str(1) == contextName;
+        if (base::StartsWith(line, "context")) {
+            isDesiredContext = base::Split(line, " ").back() == contextName;
             continue;
         }
         if (!isDesiredContext) {
@@ -66,57 +64,72 @@
     return OK;
 }
 
+// Examples of what we are looking at:
+// node 66730: u00007590061890e0 c0000759036130950 pri 0:120 hs 1 hw 1 ls 0 lw 0 is 2 iw 2 tr 1 proc 2300 1790
+// thread 2999: l 00 need_return 1 tr 0
 status_t getBinderPidInfo(BinderDebugContext context, pid_t pid, BinderPidInfo* pidInfo) {
     std::smatch match;
     static const std::regex kReferencePrefix("^\\s*node \\d+:\\s+u([0-9a-f]+)\\s+c([0-9a-f]+)\\s+");
     static const std::regex kThreadPrefix("^\\s*thread \\d+:\\s+l\\s+(\\d)(\\d)");
     std::string contextStr = contextToString(context);
     status_t ret = scanBinderContext(pid, contextStr, [&](const std::string& line) {
-        if (std::regex_search(line, match, kReferencePrefix)) {
-            const std::string& ptrString = "0x" + match.str(2); // use number after c
-            uint64_t ptr;
-            if (!::android::base::ParseUint(ptrString.c_str(), &ptr)) {
-                // Should not reach here, but just be tolerant.
-                return;
-            }
-            const std::string proc = " proc ";
-            auto pos = line.rfind(proc);
-            if (pos != std::string::npos) {
-                for (const std::string& pidStr : base::Split(line.substr(pos + proc.size()), " ")) {
-                    int32_t pid;
-                    if (!::android::base::ParseInt(pidStr, &pid)) {
+        if (base::StartsWith(line, "  node")) {
+            std::vector<std::string> splitString = base::Tokenize(line, " ");
+            bool pids = false;
+            uint64_t ptr = 0;
+            for (const auto& token : splitString) {
+                if (base::StartsWith(token, "u")) {
+                    const std::string ptrString = "0x" + token.substr(1);
+                    if (!::android::base::ParseUint(ptrString.c_str(), &ptr)) {
+                        LOG(ERROR) << "Failed to parse pointer: " << ptrString;
                         return;
                     }
-                    pidInfo->refPids[ptr].push_back(pid);
+                } else {
+                    // The last numbers in the line after "proc" are all client PIDs
+                    if (token == "proc") {
+                        pids = true;
+                    } else if (pids) {
+                        int32_t pid;
+                        if (!::android::base::ParseInt(token, &pid)) {
+                            LOG(ERROR) << "Failed to parse pid int: " << token;
+                            return;
+                        }
+                        if (ptr == 0) {
+                            LOG(ERROR) << "We failed to parse the pointer, so we can't add the refPids";
+                            return;
+                        }
+                        pidInfo->refPids[ptr].push_back(pid);
+                    }
                 }
             }
+        } else if (base::StartsWith(line, "  thread")) {
+            auto pos = line.find("l ");
+            if (pos != std::string::npos) {
+                // "1" is waiting in binder driver
+                // "2" is poll. It's impossible to tell if these are in use.
+                //     and HIDL default code doesn't use it.
+                bool isInUse = line.at(pos + 2) != '1';
+                // "0" is a thread that has called into binder
+                // "1" is looper thread
+                // "2" is main looper thread
+                bool isBinderThread = line.at(pos + 3) != '0';
+                if (!isBinderThread) {
+                    return;
+                }
+                if (isInUse) {
+                    pidInfo->threadUsage++;
+                }
 
-            return;
-        }
-        if (std::regex_search(line, match, kThreadPrefix)) {
-            // "1" is waiting in binder driver
-            // "2" is poll. It's impossible to tell if these are in use.
-            //     and HIDL default code doesn't use it.
-            bool isInUse = match.str(1) != "1";
-            // "0" is a thread that has called into binder
-            // "1" is looper thread
-            // "2" is main looper thread
-            bool isBinderThread = match.str(2) != "0";
-            if (!isBinderThread) {
-                return;
+                pidInfo->threadCount++;
             }
-            if (isInUse) {
-                pidInfo->threadUsage++;
-            }
-
-            pidInfo->threadCount++;
-            return;
         }
-        return;
     });
     return ret;
 }
 
+// Examples of what we are looking at:
+// ref 52493: desc 910 node 52492 s 1 w 1 d 0000000000000000
+// node 29413: u00007803fc982e80 c000078042c982210 pri 0:139 hs 1 hw 1 ls 0 lw 0 is 2 iw 2 tr 1 proc 488 683
 status_t getBinderClientPids(BinderDebugContext context, pid_t pid, pid_t servicePid,
                              int32_t handle, std::vector<pid_t>* pids) {
     std::smatch match;
@@ -124,51 +137,64 @@
     std::string contextStr = contextToString(context);
     int32_t node;
     status_t ret = scanBinderContext(pid, contextStr, [&](const std::string& line) {
-        if (std::regex_search(line, match, kNodeNumber)) {
-            const std::string& descString = match.str(1);
-            int32_t desc;
-            if (!::android::base::ParseInt(descString.c_str(), &desc)) {
-                LOG(ERROR) << "Failed to parse desc int: " << descString;
-                return;
-            }
-            if (handle != desc) {
-                return;
-            }
-            const std::string& nodeString = match.str(2);
-            if (!::android::base::ParseInt(nodeString.c_str(), &node)) {
-                LOG(ERROR) << "Failed to parse node int: " << nodeString;
-                return;
-            }
+        if (!base::StartsWith(line, "  ref")) return;
+
+        std::vector<std::string> splitString = base::Tokenize(line, " ");
+        if (splitString.size() < 12) {
+            LOG(ERROR) << "Failed to parse binder_logs ref entry. Expecting size greater than 11, but got: " << splitString.size();
             return;
         }
-        return;
+        int32_t desc;
+        if (!::android::base::ParseInt(splitString[3].c_str(), &desc)) {
+            LOG(ERROR) << "Failed to parse desc int: " << splitString[3];
+            return;
+        }
+        if (handle != desc) {
+            return;
+        }
+        if (!::android::base::ParseInt(splitString[5].c_str(), &node)) {
+            LOG(ERROR) << "Failed to parse node int: " << splitString[5];
+            return;
+        }
+        LOG(INFO) << "Parsed the node: " << node;
     });
     if (ret != OK) {
         return ret;
     }
-    static const std::regex kClients("^\\s+node\\s+(\\d+).*proc\\s+([\\d+\\s*]*)");
+
     ret = scanBinderContext(servicePid, contextStr, [&](const std::string& line) {
-        if (std::regex_search(line, match, kClients)) {
-            const std::string nodeString = match.str(1);
-            int32_t matchedNode;
-            if (!::android::base::ParseInt(nodeString.c_str(), &matchedNode)) {
-                LOG(ERROR) << "Failed to parse node int: " << nodeString;
-                return;
-            }
-            if (node != matchedNode) {
-                return;
-            }
-            const std::string clients = match.str(2);
-            for (const std::string& pidStr : base::Split(clients, " ")) {
+        if (!base::StartsWith(line, "  node")) return;
+
+        std::vector<std::string> splitString = base::Tokenize(line, " ");
+        if (splitString.size() < 21) {
+            LOG(ERROR) << "Failed to parse binder_logs node entry. Expecting size greater than 20, but got: " << splitString.size();
+            return;
+        }
+
+        // remove the colon
+        const std::string nodeString = splitString[1].substr(0, splitString[1].size() - 1);
+        int32_t matchedNode;
+        if (!::android::base::ParseInt(nodeString.c_str(), &matchedNode)) {
+            LOG(ERROR) << "Failed to parse node int: " << nodeString;
+            return;
+        }
+
+        if (node != matchedNode) {
+            return;
+        }
+        bool pidsSection = false;
+        for (const auto& token : splitString) {
+            if (token == "proc") {
+                pidsSection = true;
+            } else if (pidsSection == true) {
                 int32_t pid;
-                if (!::android::base::ParseInt(pidStr, &pid)) {
+                if (!::android::base::ParseInt(token.c_str(), &pid)) {
+                    LOG(ERROR) << "Failed to parse PID int: " << token;
                     return;
                 }
                 pids->push_back(pid);
             }
-            return;
         }
-        return;
     });
     return ret;
 }
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/binderthreadstate/include/binderthreadstate/CallerUtils.h b/libs/binderthreadstate/include/binderthreadstate/CallerUtils.h
index a3e5026..54259d2 100644
--- a/libs/binderthreadstate/include/binderthreadstate/CallerUtils.h
+++ b/libs/binderthreadstate/include/binderthreadstate/CallerUtils.h
@@ -36,8 +36,12 @@
 // Based on where we are in recursion of nested binder/hwbinder calls, determine
 // which one we are closer to.
 inline static BinderCallType getCurrentServingCall() {
-    const void* hwbinderSp = android::hardware::IPCThreadState::self()->getServingStackPointer();
-    const void* binderSp = android::IPCThreadState::self()->getServingStackPointer();
+    auto* hwState = android::hardware::IPCThreadState::selfOrNull();
+    auto* state = android::IPCThreadState::selfOrNull();
+
+    // getServingStackPointer can also return nullptr
+    const void* hwbinderSp = hwState ? hwState->getServingStackPointer() : nullptr;
+    const void* binderSp = state ? state->getServingStackPointer() : nullptr;
 
     if (hwbinderSp == nullptr && binderSp == nullptr) return BinderCallType::NONE;
     if (hwbinderSp == nullptr) return BinderCallType::BINDER;
diff --git a/libs/binderthreadstate/test.cpp b/libs/binderthreadstate/test.cpp
index df1f35d..b5c4010 100644
--- a/libs/binderthreadstate/test.cpp
+++ b/libs/binderthreadstate/test.cpp
@@ -16,11 +16,16 @@
 
 #include <BnAidlStuff.h>
 #include <android-base/logging.h>
+#include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binderthreadstate/CallerUtils.h>
 #include <binderthreadstateutilstest/1.0/IHidlStuff.h>
 #include <gtest/gtest.h>
 #include <hidl/HidlTransportSupport.h>
+#include <hwbinder/IPCThreadState.h>
+
+#include <thread>
+
 #include <linux/prctl.h>
 #include <sys/prctl.h>
 
@@ -154,6 +159,20 @@
     EXPECT_TRUE(server->callLocal().isOk());
 }
 
+TEST(BinderThreadState, DoesntInitializeBinderDriver) {
+    // this is on another thread, because it's testing thread-specific
+    // state and we expect it not to be initialized.
+    std::thread([&] {
+        EXPECT_EQ(nullptr, android::IPCThreadState::selfOrNull());
+        EXPECT_EQ(nullptr, android::hardware::IPCThreadState::selfOrNull());
+
+        (void)getCurrentServingCall();
+
+        EXPECT_EQ(nullptr, android::IPCThreadState::selfOrNull());
+        EXPECT_EQ(nullptr, android::hardware::IPCThreadState::selfOrNull());
+    }).join();
+}
+
 TEST(BindThreadState, RemoteHidlCall) {
     auto stuff = IHidlStuff::getService(id2name(kP1Id));
     ASSERT_NE(nullptr, stuff);
diff --git a/libs/bufferqueueconverter/Android.bp b/libs/bufferqueueconverter/Android.bp
index 5f145a1..d4605ea 100644
--- a/libs/bufferqueueconverter/Android.bp
+++ b/libs/bufferqueueconverter/Android.bp
@@ -13,7 +13,7 @@
     export_include_dirs: ["include"],
 }
 
-cc_library_shared {
+cc_library {
     name: "libbufferqueueconverter",
     vendor_available: true,
     vndk: {
diff --git a/libs/dumputils/dump_utils.cpp b/libs/dumputils/dump_utils.cpp
index 067ce17..5eb3308 100644
--- a/libs/dumputils/dump_utils.cpp
+++ b/libs/dumputils/dump_utils.cpp
@@ -16,6 +16,7 @@
 #include <set>
 
 #include <android-base/file.h>
+#include <android-base/parseint.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
@@ -61,7 +62,10 @@
         "android.hardware.audio@7.0::IDevicesFactory",
         "android.hardware.automotive.audiocontrol@1.0::IAudioControl",
         "android.hardware.automotive.audiocontrol@2.0::IAudioControl",
+        "android.hardware.automotive.can@1.0::ICanBus",
+        "android.hardware.automotive.can@1.0::ICanController",
         "android.hardware.automotive.evs@1.0::IEvsCamera",
+        "android.hardware.automotive.sv@1.0::ISurroundViewService",
         "android.hardware.automotive.vehicle@2.0::IVehicle",
         "android.hardware.biometrics.face@1.0::IBiometricsFace",
         "android.hardware.biometrics.fingerprint@2.1::IBiometricsFingerprint",
@@ -86,7 +90,12 @@
 /* list of hal interface to dump containing process during native dumps */
 static const std::vector<std::string> aidl_interfaces_to_dump {
         "android.hardware.automotive.audiocontrol.IAudioControl",
+        "android.hardware.automotive.can.ICanController",
         "android.hardware.automotive.evs.IEvsEnumerator",
+        "android.hardware.automotive.ivn.IIvnAndroidDevice",
+        "android.hardware.automotive.occupant_awareness.IOccupantAwareness",
+        "android.hardware.automotive.remoteaccess.IRemoteAccess",
+        "android.hardware.automotive.vehicle.IVehicle",
         "android.hardware.biometrics.face.IBiometricsFace",
         "android.hardware.biometrics.fingerprint.IBiometricsFingerprint",
         "android.hardware.camera.provider.ICameraProvider",
@@ -210,3 +219,18 @@
     return cmdline == "zygote" || cmdline == "zygote64" || cmdline == "usap32" ||
             cmdline == "usap64" || cmdline == "webview_zygote";
 }
+
+bool IsCached(int pid) {
+    std::string oom_score_adj;
+    if (!android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/oom_score_adj",
+                                                                     pid),
+                                         &oom_score_adj)) {
+        return false;
+    }
+    int32_t oom_score_adj_value;
+    if (!android::base::ParseInt(android::base::Trim(oom_score_adj), &oom_score_adj_value)) {
+        return false;
+    }
+    // An OOM score greater than 900 indicates a cached process.
+    return oom_score_adj_value >= 900;
+}
diff --git a/libs/dumputils/include/dumputils/dump_utils.h b/libs/dumputils/include/dumputils/dump_utils.h
index 7c5329d..f973d9f 100644
--- a/libs/dumputils/include/dumputils/dump_utils.h
+++ b/libs/dumputils/include/dumputils/dump_utils.h
@@ -25,4 +25,6 @@
 
 bool IsZygote(int pid);
 
+bool IsCached(int pid);
+
 #endif  // DUMPUTILS_H_
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/algorithm_test.cpp b/libs/ftl/algorithm_test.cpp
index 8052caf..487b1b8 100644
--- a/libs/ftl/algorithm_test.cpp
+++ b/libs/ftl/algorithm_test.cpp
@@ -47,4 +47,20 @@
   EXPECT_EQ(opt->get(), ftl::StaticVector("tiramisu"sv));
 }
 
+TEST(Algorithm, StaticRef) {
+  using namespace std::string_view_literals;
+
+  const ftl::SmallMap map = ftl::init::map(13, "tiramisu"sv)(14, "upside-down cake"sv);
+  ASSERT_EQ("???"sv,
+            map.get(20).or_else(ftl::static_ref<std::string_view>([] { return "???"sv; }))->get());
+
+  using Map = decltype(map);
+
+  ASSERT_EQ("snow cone"sv,
+            ftl::find_if(map, [](const auto& pair) { return pair.second.front() == 's'; })
+                .transform(ftl::to_mapped_ref<Map>)
+                .or_else(ftl::static_ref<std::string_view>([] { return "snow cone"sv; }))
+                ->get());
+}
+
 }  // namespace android::test
diff --git a/libs/ftl/flags_test.cpp b/libs/ftl/flags_test.cpp
index eea052b..1279d11 100644
--- a/libs/ftl/flags_test.cpp
+++ b/libs/ftl/flags_test.cpp
@@ -35,6 +35,7 @@
 
 TEST(Flags, Any) {
     Flags<TestFlags> flags = TestFlags::ONE | TestFlags::TWO;
+    ASSERT_TRUE(flags.any());
     ASSERT_TRUE(flags.any(TestFlags::ONE));
     ASSERT_TRUE(flags.any(TestFlags::TWO));
     ASSERT_FALSE(flags.any(TestFlags::THREE));
@@ -42,6 +43,9 @@
     ASSERT_TRUE(flags.any(TestFlags::TWO | TestFlags::THREE));
     ASSERT_TRUE(flags.any(TestFlags::ONE | TestFlags::THREE));
     ASSERT_TRUE(flags.any(TestFlags::ONE | TestFlags::TWO | TestFlags::THREE));
+
+    Flags<TestFlags> emptyFlags;
+    ASSERT_FALSE(emptyFlags.any());
 }
 
 TEST(Flags, All) {
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/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp
index 46dd62d..5fbae3c 100644
--- a/libs/graphicsenv/GraphicsEnv.cpp
+++ b/libs/graphicsenv/GraphicsEnv.cpp
@@ -433,61 +433,24 @@
     return (mUseAngle == YES) ? true : false;
 }
 
-bool GraphicsEnv::angleIsSystemDriver() {
-    // Make sure we are init'ed
-    if (mAngleAppName.empty()) {
-        ALOGV("App name is empty. setAngleInfo() has not been called to enable ANGLE.");
-        return false;
-    }
-
-    return (mAngleIsSystemDriver == YES) ? true : false;
-}
-
-bool GraphicsEnv::shouldForceLegacyDriver() {
-    // Make sure we are init'ed
-    if (mAngleAppName.empty()) {
-        ALOGV("App name is empty. setAngleInfo() has not been called to enable ANGLE.");
-        return false;
-    }
-
-    return (mAngleIsSystemDriver == YES && mUseAngle == NO) ? true : false;
-}
-
-std::string GraphicsEnv::getLegacySuffix() {
-    return mLegacyDriverSuffix;
-}
-
 void GraphicsEnv::updateUseAngle() {
-    mUseAngle = NO;
-
     const char* ANGLE_PREFER_ANGLE = "angle";
-    const char* ANGLE_PREFER_LEGACY = "legacy";
-    // The following is a deprecated version of "legacy"
     const char* ANGLE_PREFER_NATIVE = "native";
 
     mUseAngle = NO;
     if (mAngleDeveloperOptIn == ANGLE_PREFER_ANGLE) {
-        ALOGI("Using ANGLE, the %s GLES driver for package '%s'",
-              mAngleIsSystemDriver == YES ? "system" : "optional", mAngleAppName.c_str());
+        ALOGV("User set \"Developer Options\" to force the use of ANGLE");
         mUseAngle = YES;
-    } else if (mAngleDeveloperOptIn == ANGLE_PREFER_LEGACY ||
-               mAngleDeveloperOptIn == ANGLE_PREFER_NATIVE) {
-        ALOGI("Using the (%s) Legacy GLES driver for package '%s'",
-              mAngleIsSystemDriver == YES ? "optional" : "system", mAngleAppName.c_str());
+    } else if (mAngleDeveloperOptIn == ANGLE_PREFER_NATIVE) {
+        ALOGV("User set \"Developer Options\" to force the use of Native");
     } else {
         ALOGV("User set invalid \"Developer Options\": '%s'", mAngleDeveloperOptIn.c_str());
     }
 }
 
 void GraphicsEnv::setAngleInfo(const std::string path, const std::string appName,
-                               const bool angleIsSystemDriver, const std::string developerOptIn,
+                               const std::string developerOptIn,
                                const std::vector<std::string> eglFeatures) {
-    // Set whether ANGLE is the system driver:
-    mAngleIsSystemDriver = angleIsSystemDriver ? YES : NO;
-
-    // Note: Given the current logic and lack of the old rules file processing,
-    // there seems to be little chance that mUseAngle != UNKNOWN.  Leave this
-    // for now, even though it seems outdated.
     if (mUseAngle != UNKNOWN) {
         // We've already figured out an answer for this app, so just return.
         ALOGV("Already evaluated the rules file for '%s': use ANGLE = %s", appName.c_str(),
@@ -508,25 +471,6 @@
     updateUseAngle();
 }
 
-void GraphicsEnv::setLegacyDriverInfo(const std::string appName, const bool angleIsSystemDriver,
-                                      const std::string legacyDriverName) {
-    ALOGV("setting legacy app name to '%s'", appName.c_str());
-    mAngleAppName = appName;
-
-    // Force the use of the legacy driver instead of ANGLE
-    const char* ANGLE_PREFER_LEGACY = "legacy";
-    mAngleDeveloperOptIn = ANGLE_PREFER_LEGACY;
-    ALOGV("setting ANGLE application opt-in to 'legacy'");
-
-    // Set whether ANGLE is the system driver:
-    mAngleIsSystemDriver = angleIsSystemDriver ? YES : NO;
-
-    mLegacyDriverSuffix = legacyDriverName;
-
-    // Update the current status of whether we should use ANGLE or not
-    updateUseAngle();
-}
-
 void GraphicsEnv::setLayerPaths(NativeLoaderNamespace* appNamespace, const std::string layerPaths) {
     if (mLayerPaths.empty()) {
         mLayerPaths = layerPaths;
@@ -689,4 +633,13 @@
     return mAngleNamespace;
 }
 
+void GraphicsEnv::nativeToggleAngleAsSystemDriver(bool enabled) {
+    const sp<IGpuService> gpuService = getGpuService();
+    if (!gpuService) {
+        ALOGE("No GPU service");
+        return;
+    }
+    gpuService->toggleAngleAsSystemDriver(enabled);
+}
+
 } // namespace android
diff --git a/libs/graphicsenv/IGpuService.cpp b/libs/graphicsenv/IGpuService.cpp
index ceb52f7..4c070ae 100644
--- a/libs/graphicsenv/IGpuService.cpp
+++ b/libs/graphicsenv/IGpuService.cpp
@@ -78,6 +78,15 @@
                            IBinder::FLAG_ONEWAY);
     }
 
+    void toggleAngleAsSystemDriver(bool enabled) override {
+        Parcel data, reply;
+        data.writeInterfaceToken(IGpuService::getInterfaceDescriptor());
+        data.writeBool(enabled);
+
+        remote()->transact(BnGpuService::TOGGLE_ANGLE_AS_SYSTEM_DRIVER, data, &reply,
+                           IBinder::FLAG_ONEWAY);
+    }
+
     std::string getUpdatableDriverPath() override {
         Parcel data, reply;
         data.writeInterfaceToken(IGpuService::getInterfaceDescriptor());
@@ -189,6 +198,15 @@
 
             return OK;
         }
+        case TOGGLE_ANGLE_AS_SYSTEM_DRIVER: {
+            CHECK_INTERFACE(IGpuService, data, reply);
+
+            bool enableAngleAsSystemDriver;
+            if ((status = data.readBool(&enableAngleAsSystemDriver)) != OK) return status;
+
+            toggleAngleAsSystemDriver(enableAngleAsSystemDriver);
+            return OK;
+        }
         default:
             return BBinder::onTransact(code, data, reply, flags);
     }
diff --git a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
index b58a6d9..f9b234a 100644
--- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
+++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
@@ -100,28 +100,17 @@
     bool shouldUseAngle(std::string appName);
     // Check if this app process should use ANGLE.
     bool shouldUseAngle();
-    // If ANGLE is the system GLES driver
-    bool angleIsSystemDriver();
-    // If should use legacy driver instead of a system ANGLE driver
-    bool shouldForceLegacyDriver();
     // Set a search path for loading ANGLE libraries. The path is a list of
     // directories separated by ':'. A directory can be contained in a zip file
     // (libraries must be stored uncompressed and page aligned); such elements
     // in the search path must have a '!' after the zip filename, e.g.
     //     /system/app/ANGLEPrebuilt/ANGLEPrebuilt.apk!/lib/arm64-v8a
-    void setAngleInfo(const std::string path, const std::string appName,
-                      const bool angleIsSystemDriver, std::string devOptIn,
+    void setAngleInfo(const std::string path, const std::string appName, std::string devOptIn,
                       const std::vector<std::string> eglFeatures);
-    // Set the state so that the legacy driver will be used, and in case ANGLE
-    // is the system driver, provide the name of the legacy driver.
-    void setLegacyDriverInfo(const std::string appName, const bool angleIsSystemDriver,
-                             const std::string legacyDriverName);
     // Get the ANGLE driver namespace.
     android_namespace_t* getAngleNamespace();
     // Get the app name for ANGLE debug message.
     std::string& getAngleAppName();
-    // Get the legacy driver's suffix name.
-    std::string getLegacySuffix();
 
     const std::vector<std::string>& getAngleEglFeatures();
 
@@ -142,6 +131,8 @@
     const std::string& getDebugLayers();
     // Get the debug layers to load.
     const std::string& getDebugLayersGLES();
+    // Set the persist.graphics.egl system property value.
+    void nativeToggleAngleAsSystemDriver(bool enabled);
 
 private:
     enum UseAngle { UNKNOWN, YES, NO };
@@ -176,10 +167,6 @@
     std::string mAngleDeveloperOptIn;
     // ANGLE EGL features;
     std::vector<std::string> mAngleEglFeatures;
-    // ANGLE is System Driver flag.
-    UseAngle mAngleIsSystemDriver = UNKNOWN;
-    // Legacy driver name to use when ANGLE is the system driver.
-    std::string mLegacyDriverSuffix;
     // Use ANGLE flag.
     UseAngle mUseAngle = UNKNOWN;
     // Vulkan debug layers libs.
diff --git a/libs/graphicsenv/include/graphicsenv/IGpuService.h b/libs/graphicsenv/include/graphicsenv/IGpuService.h
index b708b0f..e3857d2 100644
--- a/libs/graphicsenv/include/graphicsenv/IGpuService.h
+++ b/libs/graphicsenv/include/graphicsenv/IGpuService.h
@@ -50,6 +50,9 @@
     // setter and getter for updatable driver path.
     virtual void setUpdatableDriverPath(const std::string& driverPath) = 0;
     virtual std::string getUpdatableDriverPath() = 0;
+
+    // sets ANGLE as system GLES driver if enabled==true by setting persist.graphics.egl to true.
+    virtual void toggleAngleAsSystemDriver(bool enabled) = 0;
 };
 
 class BnGpuService : public BnInterface<IGpuService> {
@@ -59,6 +62,7 @@
         SET_TARGET_STATS,
         SET_UPDATABLE_DRIVER_PATH,
         GET_UPDATABLE_DRIVER_PATH,
+        TOGGLE_ANGLE_AS_SYSTEM_DRIVER,
         // Always append new enum to the end.
     };
 
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 21900a0..bf34987 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -66,6 +66,19 @@
     ],
 }
 
+filegroup {
+    name: "android_gui_aidl",
+    srcs: [
+        "android/gui/DisplayInfo.aidl",
+        "android/gui/FocusRequest.aidl",
+        "android/gui/InputApplicationInfo.aidl",
+        "android/gui/IWindowInfosListener.aidl",
+        "android/gui/IWindowInfosReportedListener.aidl",
+        "android/gui/WindowInfo.aidl",
+        "android/gui/WindowInfosUpdate.aidl",
+    ],
+}
+
 cc_library_static {
     name: "libgui_window_info_static",
     vendor_available: true,
@@ -78,9 +91,11 @@
         "android/gui/InputApplicationInfo.aidl",
         "android/gui/IWindowInfosListener.aidl",
         "android/gui/IWindowInfosReportedListener.aidl",
+        "android/gui/WindowInfosUpdate.aidl",
         "android/gui/WindowInfo.aidl",
         "DisplayInfo.cpp",
         "WindowInfo.cpp",
+        "WindowInfosUpdate.cpp",
     ],
 
     shared_libs: [
@@ -118,6 +133,9 @@
     name: "libgui_aidl",
     srcs: ["aidl/**/*.aidl"],
     path: "aidl/",
+    aidl: {
+        deps: [":android_gui_aidl"],
+    },
 }
 
 filegroup {
@@ -199,6 +217,7 @@
         "DebugEGLImageTracker.cpp",
         "DisplayEventDispatcher.cpp",
         "DisplayEventReceiver.cpp",
+        "FenceMonitor.cpp",
         "GLConsumer.cpp",
         "IConsumerListener.cpp",
         "IGraphicBufferConsumer.cpp",
@@ -208,6 +227,7 @@
         "ITransactionCompletedListener.cpp",
         "LayerDebugInfo.cpp",
         "LayerMetadata.cpp",
+        "LayerStatePermissions.cpp",
         "LayerState.cpp",
         "OccupancyTracker.cpp",
         "StreamSplitter.cpp",
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index cf8b13f..5c324b2 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),
@@ -577,7 +582,8 @@
     // Only update mSize for destination bounds if the incoming buffer matches the requested size.
     // Otherwise, it could cause stretching since the destination bounds will update before the
     // buffer with the new size is acquired.
-    if (mRequestedSize == getBufferSize(bufferItem)) {
+    if (mRequestedSize == getBufferSize(bufferItem) ||
+        bufferItem.mScalingMode != NATIVE_WINDOW_SCALING_MODE_FREEZE) {
         mSize = mRequestedSize;
     }
     Rect crop = computeCrop(bufferItem);
@@ -795,34 +801,24 @@
     mDequeueTimestamps.erase(bufferId);
 };
 
-void BLASTBufferQueue::syncNextTransaction(
+bool BLASTBufferQueue::syncNextTransaction(
         std::function<void(SurfaceComposerClient::Transaction*)> callback,
         bool acquireSingleBuffer) {
-    std::function<void(SurfaceComposerClient::Transaction*)> prevCallback = nullptr;
-    SurfaceComposerClient::Transaction* prevTransaction = nullptr;
+    LOG_ALWAYS_FATAL_IF(!callback,
+                        "BLASTBufferQueue: callback passed in to syncNextTransaction must not be "
+                        "NULL");
 
-    {
-        std::lock_guard _lock{mMutex};
-        BBQ_TRACE();
-        // We're about to overwrite the previous call so we should invoke that callback
-        // immediately.
-        if (mTransactionReadyCallback) {
-            prevCallback = mTransactionReadyCallback;
-            prevTransaction = mSyncTransaction;
-        }
-
-        mTransactionReadyCallback = callback;
-        if (callback) {
-            mSyncTransaction = new SurfaceComposerClient::Transaction();
-        } else {
-            mSyncTransaction = nullptr;
-        }
-        mAcquireSingleBuffer = mTransactionReadyCallback ? acquireSingleBuffer : true;
+    std::lock_guard _lock{mMutex};
+    BBQ_TRACE();
+    if (mTransactionReadyCallback) {
+        ALOGW("Attempting to overwrite transaction callback in syncNextTransaction");
+        return false;
     }
 
-    if (prevCallback) {
-        prevCallback(prevTransaction);
-    }
+    mTransactionReadyCallback = callback;
+    mSyncTransaction = new SurfaceComposerClient::Transaction();
+    mAcquireSingleBuffer = acquireSingleBuffer;
+    return true;
 }
 
 void BLASTBufferQueue::stopContinuousSyncTransaction() {
@@ -830,20 +826,35 @@
     SurfaceComposerClient::Transaction* prevTransaction = nullptr;
     {
         std::lock_guard _lock{mMutex};
-        bool invokeCallback = mTransactionReadyCallback && !mAcquireSingleBuffer;
-        if (invokeCallback) {
-            prevCallback = mTransactionReadyCallback;
-            prevTransaction = mSyncTransaction;
+        if (mAcquireSingleBuffer || !mTransactionReadyCallback) {
+            ALOGW("Attempting to stop continuous sync when none are active");
+            return;
         }
+
+        prevCallback = mTransactionReadyCallback;
+        prevTransaction = mSyncTransaction;
+
         mTransactionReadyCallback = nullptr;
         mSyncTransaction = nullptr;
         mAcquireSingleBuffer = true;
     }
+
     if (prevCallback) {
         prevCallback(prevTransaction);
     }
 }
 
+void BLASTBufferQueue::clearSyncTransaction() {
+    std::lock_guard _lock{mMutex};
+    if (!mAcquireSingleBuffer) {
+        ALOGW("Attempting to clear sync transaction when none are active");
+        return;
+    }
+
+    mTransactionReadyCallback = nullptr;
+    mSyncTransaction = nullptr;
+}
+
 bool BLASTBufferQueue::rejectBuffer(const BufferItem& item) {
     if (item.mScalingMode != NATIVE_WINDOW_SCALING_MODE_FREEZE) {
         // Only reject buffers if scaling mode is freeze.
@@ -1069,8 +1080,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 +1094,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 +1121,9 @@
         }
         return BufferQueueProducer::query(what, value);
     }
+
+private:
+    const wp<BLASTBufferQueue> mBLASTBufferQueue;
 };
 
 // Similar to BufferQueue::createBufferQueue but creates an adapter specific bufferqueue producer.
@@ -1103,7 +1138,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 +1151,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/BufferQueueConsumer.cpp b/libs/gui/BufferQueueConsumer.cpp
index 7f7a043..5217209 100644
--- a/libs/gui/BufferQueueConsumer.cpp
+++ b/libs/gui/BufferQueueConsumer.cpp
@@ -33,6 +33,7 @@
 #include <gui/BufferQueueCore.h>
 #include <gui/IConsumerListener.h>
 #include <gui/IProducerListener.h>
+#include <gui/TraceUtils.h>
 
 #include <private/gui/BufferQueueThreadState.h>
 #ifndef __ANDROID_VNDK__
@@ -646,7 +647,7 @@
 
 status_t BufferQueueConsumer::setMaxAcquiredBufferCount(
         int maxAcquiredBuffers) {
-    ATRACE_CALL();
+    ATRACE_FORMAT("%s(%d)", __func__, maxAcquiredBuffers);
 
     if (maxAcquiredBuffers < 1 ||
             maxAcquiredBuffers > BufferQueueCore::MAX_MAX_ACQUIRED_BUFFERS) {
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index 5fe5e71..9a2343b 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -35,6 +35,7 @@
 #include <gui/GLConsumer.h>
 #include <gui/IConsumerListener.h>
 #include <gui/IProducerListener.h>
+#include <gui/TraceUtils.h>
 #include <private/gui/BufferQueueThreadState.h>
 
 #include <utils/Log.h>
@@ -119,7 +120,13 @@
 
 status_t BufferQueueProducer::setMaxDequeuedBufferCount(
         int maxDequeuedBuffers) {
-    ATRACE_CALL();
+    int maxBufferCount;
+    return setMaxDequeuedBufferCount(maxDequeuedBuffers, &maxBufferCount);
+}
+
+status_t BufferQueueProducer::setMaxDequeuedBufferCount(int maxDequeuedBuffers,
+                                                        int* maxBufferCount) {
+    ATRACE_FORMAT("%s(%d)", __func__, maxDequeuedBuffers);
     BQ_LOGV("setMaxDequeuedBufferCount: maxDequeuedBuffers = %d",
             maxDequeuedBuffers);
 
@@ -134,6 +141,8 @@
             return NO_INIT;
         }
 
+        *maxBufferCount = mCore->getMaxBufferCountLocked();
+
         if (maxDequeuedBuffers == mCore->mMaxDequeuedBufferCount) {
             return NO_ERROR;
         }
@@ -183,6 +192,7 @@
             return BAD_VALUE;
         }
         mCore->mMaxDequeuedBufferCount = maxDequeuedBuffers;
+        *maxBufferCount = mCore->getMaxBufferCountLocked();
         VALIDATE_CONSISTENCY();
         if (delta < 0) {
             listener = mCore->mConsumerListener;
@@ -493,6 +503,20 @@
         if ((buffer == nullptr) ||
                 buffer->needsReallocation(width, height, format, BQ_LAYER_COUNT, usage))
         {
+            if (CC_UNLIKELY(ATRACE_ENABLED())) {
+                if (buffer == nullptr) {
+                    ATRACE_FORMAT_INSTANT("%s buffer reallocation: null", mConsumerName.string());
+                } else {
+                    ATRACE_FORMAT_INSTANT("%s buffer reallocation actual %dx%d format:%d "
+                                          "layerCount:%d "
+                                          "usage:%d requested: %dx%d format:%d layerCount:%d "
+                                          "usage:%d ",
+                                          mConsumerName.string(), width, height, format,
+                                          BQ_LAYER_COUNT, usage, buffer->getWidth(),
+                                          buffer->getHeight(), buffer->getPixelFormat(),
+                                          buffer->getLayerCount(), buffer->getUsage());
+                }
+            }
             mSlots[found].mAcquireCalled = false;
             mSlots[found].mGraphicBuffer = nullptr;
             mSlots[found].mRequestBufferCalled = false;
diff --git a/libs/gui/FenceMonitor.cpp b/libs/gui/FenceMonitor.cpp
new file mode 100644
index 0000000..230c81a
--- /dev/null
+++ b/libs/gui/FenceMonitor.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.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <gui/FenceMonitor.h>
+#include <gui/TraceUtils.h>
+#include <log/log.h>
+
+#include <thread>
+
+namespace android::gui {
+
+FenceMonitor::FenceMonitor(const char* name) : mName(name), mFencesQueued(0), mFencesSignaled(0) {
+    std::thread thread(&FenceMonitor::loop, this);
+    pthread_setname_np(thread.native_handle(), mName);
+    thread.detach();
+}
+
+void FenceMonitor::queueFence(const sp<Fence>& fence) {
+    char message[64];
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (fence->getSignalTime() != Fence::SIGNAL_TIME_PENDING) {
+        snprintf(message, sizeof(message), "%s fence %u has signaled", mName, mFencesQueued);
+        ATRACE_NAME(message);
+        // Need an increment on both to make the trace number correct.
+        mFencesQueued++;
+        mFencesSignaled++;
+        return;
+    }
+    snprintf(message, sizeof(message), "Trace %s fence %u", mName, mFencesQueued);
+    ATRACE_NAME(message);
+
+    mQueue.push_back(fence);
+    mCondition.notify_one();
+    mFencesQueued++;
+    ATRACE_INT(mName, int32_t(mQueue.size()));
+}
+
+void FenceMonitor::loop() {
+    while (true) {
+        threadLoop();
+    }
+}
+
+void FenceMonitor::threadLoop() {
+    sp<Fence> fence;
+    uint32_t fenceNum;
+    {
+        std::unique_lock<std::mutex> lock(mMutex);
+        while (mQueue.empty()) {
+            mCondition.wait(lock);
+        }
+        fence = mQueue[0];
+        fenceNum = mFencesSignaled;
+    }
+    {
+        char message[64];
+        snprintf(message, sizeof(message), "waiting for %s %u", mName, fenceNum);
+        ATRACE_NAME(message);
+
+        status_t result = fence->waitForever(message);
+        if (result != OK) {
+            ALOGE("Error waiting for fence: %d", result);
+        }
+    }
+    {
+        std::lock_guard<std::mutex> lock(mMutex);
+        mQueue.pop_front();
+        mFencesSignaled++;
+        ATRACE_INT(mName, int32_t(mQueue.size()));
+    }
+}
+
+} // namespace android::gui
\ No newline at end of file
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/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index cefb9a7..b526a6c 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -59,15 +59,13 @@
 
     virtual ~BpSurfaceComposer();
 
-    status_t setTransactionState(const FrameTimelineInfo& frameTimelineInfo,
-                                 Vector<ComposerState>& state, const Vector<DisplayState>& displays,
-                                 uint32_t flags, const sp<IBinder>& applyToken,
-                                 const InputWindowCommands& commands, int64_t desiredPresentTime,
-                                 bool isAutoTimestamp,
-                                 const std::vector<client_cache_t>& uncacheBuffers,
-                                 bool hasListenerCallbacks,
-                                 const std::vector<ListenerCallbacks>& listenerCallbacks,
-                                 uint64_t transactionId) override {
+    status_t setTransactionState(
+            const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
+            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            InputWindowCommands commands, int64_t desiredPresentTime, bool isAutoTimestamp,
+            const std::vector<client_cache_t>& uncacheBuffers, bool hasListenerCallbacks,
+            const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId,
+            const std::vector<uint64_t>& mergedTransactionIds) override {
         Parcel data, reply;
         data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
 
@@ -103,6 +101,11 @@
 
         SAFE_PARCEL(data.writeUint64, transactionId);
 
+        SAFE_PARCEL(data.writeUint32, static_cast<uint32_t>(mergedTransactionIds.size()));
+        for (auto mergedTransactionId : mergedTransactionIds) {
+            SAFE_PARCEL(data.writeUint64, mergedTransactionId);
+        }
+
         if (flags & ISurfaceComposer::eOneWay) {
             return remote()->transact(BnSurfaceComposer::SET_TRANSACTION_STATE,
                     data, &reply, IBinder::FLAG_ONEWAY);
@@ -187,10 +190,16 @@
             uint64_t transactionId = -1;
             SAFE_PARCEL(data.readUint64, &transactionId);
 
+            SAFE_PARCEL_READ_SIZE(data.readUint32, &count, data.dataSize());
+            std::vector<uint64_t> mergedTransactions(count);
+            for (size_t i = 0; i < count; i++) {
+                SAFE_PARCEL(data.readUint64, &mergedTransactions[i]);
+            }
+
             return setTransactionState(frameTimelineInfo, state, displays, stateFlags, applyToken,
-                                       inputWindowCommands, desiredPresentTime, isAutoTimestamp,
-                                       uncacheBuffers, hasListenerCallbacks, listenerCallbacks,
-                                       transactionId);
+                                       std::move(inputWindowCommands), desiredPresentTime,
+                                       isAutoTimestamp, uncacheBuffers, hasListenerCallbacks,
+                                       listenerCallbacks, transactionId, mergedTransactions);
         }
         default: {
             return BBinder::onTransact(code, data, reply, flags);
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index b391337..2322b70 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -190,8 +190,8 @@
     }
     SAFE_PARCEL(output.writeParcelable, trustedPresentationThresholds);
     SAFE_PARCEL(output.writeParcelable, trustedPresentationListener);
-    SAFE_PARCEL(output.writeFloat, currentSdrHdrRatio);
-    SAFE_PARCEL(output.writeFloat, desiredSdrHdrRatio);
+    SAFE_PARCEL(output.writeFloat, currentHdrSdrRatio);
+    SAFE_PARCEL(output.writeFloat, desiredHdrSdrRatio);
     SAFE_PARCEL(output.writeInt32, static_cast<int32_t>(cachingHint))
     return NO_ERROR;
 }
@@ -335,9 +335,9 @@
     SAFE_PARCEL(input.readParcelable, &trustedPresentationListener);
 
     SAFE_PARCEL(input.readFloat, &tmpFloat);
-    currentSdrHdrRatio = tmpFloat;
+    currentHdrSdrRatio = tmpFloat;
     SAFE_PARCEL(input.readFloat, &tmpFloat);
-    desiredSdrHdrRatio = tmpFloat;
+    desiredHdrSdrRatio = tmpFloat;
 
     int32_t tmpInt32;
     SAFE_PARCEL(input.readInt32, &tmpInt32);
@@ -592,8 +592,8 @@
     }
     if (other.what & eExtendedRangeBrightnessChanged) {
         what |= eExtendedRangeBrightnessChanged;
-        desiredSdrHdrRatio = other.desiredSdrHdrRatio;
-        currentSdrHdrRatio = other.currentSdrHdrRatio;
+        desiredHdrSdrRatio = other.desiredHdrSdrRatio;
+        currentHdrSdrRatio = other.currentHdrSdrRatio;
     }
     if (other.what & eCachingHintChanged) {
         what |= eCachingHintChanged;
@@ -747,8 +747,8 @@
     CHECK_DIFF(diff, eCropChanged, other, crop);
     if (other.what & eBufferChanged) diff |= eBufferChanged;
     CHECK_DIFF(diff, eDataspaceChanged, other, dataspace);
-    CHECK_DIFF2(diff, eExtendedRangeBrightnessChanged, other, currentSdrHdrRatio,
-                desiredSdrHdrRatio);
+    CHECK_DIFF2(diff, eExtendedRangeBrightnessChanged, other, currentHdrSdrRatio,
+                desiredHdrSdrRatio);
     CHECK_DIFF(diff, eCachingHintChanged, other, cachingHint);
     CHECK_DIFF(diff, eHdrMetadataChanged, other, hdrMetadata);
     if (other.what & eSurfaceDamageRegionChanged &&
@@ -897,6 +897,11 @@
     SAFE_PARCEL(output->writeInt32, static_cast<int32_t>(dataspace));
     SAFE_PARCEL(output->writeBool, allowProtected);
     SAFE_PARCEL(output->writeBool, grayscale);
+    SAFE_PARCEL(output->writeInt32, excludeHandles.size());
+    for (auto& excludeHandle : excludeHandles) {
+        SAFE_PARCEL(output->writeStrongBinder, excludeHandle);
+    }
+    SAFE_PARCEL(output->writeBool, hintForSeamlessTransition);
     return NO_ERROR;
 }
 
@@ -913,6 +918,15 @@
     dataspace = static_cast<ui::Dataspace>(value);
     SAFE_PARCEL(input->readBool, &allowProtected);
     SAFE_PARCEL(input->readBool, &grayscale);
+    int32_t numExcludeHandles = 0;
+    SAFE_PARCEL_READ_SIZE(input->readInt32, &numExcludeHandles, input->dataSize());
+    excludeHandles.reserve(numExcludeHandles);
+    for (int i = 0; i < numExcludeHandles; i++) {
+        sp<IBinder> binder;
+        SAFE_PARCEL(input->readStrongBinder, &binder);
+        excludeHandles.emplace(binder);
+    }
+    SAFE_PARCEL(input->readBool, &hintForSeamlessTransition);
     return NO_ERROR;
 }
 
@@ -940,10 +954,6 @@
     SAFE_PARCEL(CaptureArgs::writeToParcel, output);
 
     SAFE_PARCEL(output->writeStrongBinder, layerHandle);
-    SAFE_PARCEL(output->writeInt32, excludeHandles.size());
-    for (auto el : excludeHandles) {
-        SAFE_PARCEL(output->writeStrongBinder, el);
-    }
     SAFE_PARCEL(output->writeBool, childrenOnly);
     return NO_ERROR;
 }
@@ -953,15 +963,6 @@
 
     SAFE_PARCEL(input->readStrongBinder, &layerHandle);
 
-    int32_t numExcludeHandles = 0;
-    SAFE_PARCEL_READ_SIZE(input->readInt32, &numExcludeHandles, input->dataSize());
-    excludeHandles.reserve(numExcludeHandles);
-    for (int i = 0; i < numExcludeHandles; i++) {
-        sp<IBinder> binder;
-        SAFE_PARCEL(input->readStrongBinder, &binder);
-        excludeHandles.emplace(binder);
-    }
-
     SAFE_PARCEL(input->readBool, &childrenOnly);
     return NO_ERROR;
 }
diff --git a/libs/gui/LayerStatePermissions.cpp b/libs/gui/LayerStatePermissions.cpp
new file mode 100644
index 0000000..28697ca
--- /dev/null
+++ b/libs/gui/LayerStatePermissions.cpp
@@ -0,0 +1,58 @@
+/*
+ * 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 <binder/IPCThreadState.h>
+#include <gui/LayerStatePermissions.h>
+#include <private/android_filesystem_config.h>
+#ifndef __ANDROID_VNDK__
+#include <binder/PermissionCache.h>
+#endif // __ANDROID_VNDK__
+#include <gui/LayerState.h>
+
+namespace android {
+std::unordered_map<std::string, int> LayerStatePermissions::mPermissionMap = {
+        // If caller has ACCESS_SURFACE_FLINGER, they automatically get ROTATE_SURFACE_FLINGER
+        // permission, as well
+        {"android.permission.ACCESS_SURFACE_FLINGER",
+         layer_state_t::Permission::ACCESS_SURFACE_FLINGER |
+                 layer_state_t::Permission::ROTATE_SURFACE_FLINGER},
+        {"android.permission.ROTATE_SURFACE_FLINGER",
+         layer_state_t::Permission::ROTATE_SURFACE_FLINGER},
+        {"android.permission.INTERNAL_SYSTEM_WINDOW",
+         layer_state_t::Permission::INTERNAL_SYSTEM_WINDOW},
+};
+
+static bool callingThreadHasPermission(const std::string& permission __attribute__((unused)),
+                                       int pid __attribute__((unused)),
+                                       int uid __attribute__((unused))) {
+#ifndef __ANDROID_VNDK__
+    return uid == AID_GRAPHICS || uid == AID_SYSTEM ||
+            PermissionCache::checkPermission(String16(permission.c_str()), pid, uid);
+#endif // __ANDROID_VNDK__
+    return false;
+}
+
+uint32_t LayerStatePermissions::getTransactionPermissions(int pid, int uid) {
+    uint32_t permissions = 0;
+    for (auto [permissionName, permissionVal] : mPermissionMap) {
+        if (callingThreadHasPermission(permissionName, pid, uid)) {
+            permissions |= permissionVal;
+        }
+    }
+
+    return permissions;
+}
+} // namespace android
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index b18bf5b..ed69100 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -30,6 +30,7 @@
 #include <android/gui/DisplayStatInfo.h>
 #include <android/native_window.h>
 
+#include <gui/FenceMonitor.h>
 #include <gui/TraceUtils.h>
 #include <utils/Log.h>
 #include <utils/NativeHandle.h>
@@ -545,82 +546,6 @@
     return NO_ERROR;
 }
 
-class FenceMonitor {
-public:
-    explicit FenceMonitor(const char* name) : mName(name), mFencesQueued(0), mFencesSignaled(0) {
-        std::thread thread(&FenceMonitor::loop, this);
-        pthread_setname_np(thread.native_handle(), mName);
-        thread.detach();
-    }
-
-    void queueFence(const sp<Fence>& fence) {
-        char message[64];
-
-        std::lock_guard<std::mutex> lock(mMutex);
-        if (fence->getSignalTime() != Fence::SIGNAL_TIME_PENDING) {
-            snprintf(message, sizeof(message), "%s fence %u has signaled", mName, mFencesQueued);
-            ATRACE_NAME(message);
-            // Need an increment on both to make the trace number correct.
-            mFencesQueued++;
-            mFencesSignaled++;
-            return;
-        }
-        snprintf(message, sizeof(message), "Trace %s fence %u", mName, mFencesQueued);
-        ATRACE_NAME(message);
-
-        mQueue.push_back(fence);
-        mCondition.notify_one();
-        mFencesQueued++;
-        ATRACE_INT(mName, int32_t(mQueue.size()));
-    }
-
-private:
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wmissing-noreturn"
-    void loop() {
-        while (true) {
-            threadLoop();
-        }
-    }
-#pragma clang diagnostic pop
-
-    void threadLoop() {
-        sp<Fence> fence;
-        uint32_t fenceNum;
-        {
-            std::unique_lock<std::mutex> lock(mMutex);
-            while (mQueue.empty()) {
-                mCondition.wait(lock);
-            }
-            fence = mQueue[0];
-            fenceNum = mFencesSignaled;
-        }
-        {
-            char message[64];
-            snprintf(message, sizeof(message), "waiting for %s %u", mName, fenceNum);
-            ATRACE_NAME(message);
-
-            status_t result = fence->waitForever(message);
-            if (result != OK) {
-                ALOGE("Error waiting for fence: %d", result);
-            }
-        }
-        {
-            std::lock_guard<std::mutex> lock(mMutex);
-            mQueue.pop_front();
-            mFencesSignaled++;
-            ATRACE_INT(mName, int32_t(mQueue.size()));
-        }
-    }
-
-    const char* mName;
-    uint32_t mFencesQueued;
-    uint32_t mFencesSignaled;
-    std::deque<sp<Fence>> mQueue;
-    std::condition_variable mCondition;
-    std::mutex mMutex;
-};
-
 void Surface::getDequeueBufferInputLocked(
         IGraphicBufferProducer::DequeueBufferInput* dequeueInput) {
     LOG_ALWAYS_FATAL_IF(dequeueInput == nullptr, "input is null");
@@ -694,7 +619,7 @@
     ALOGE_IF(fence == nullptr, "Surface::dequeueBuffer: received null Fence! buf=%d", buf);
 
     if (CC_UNLIKELY(atrace_is_tag_enabled(ATRACE_TAG_GRAPHICS))) {
-        static FenceMonitor hwcReleaseThread("HWC release");
+        static gui::FenceMonitor hwcReleaseThread("HWC release");
         hwcReleaseThread.queueFence(fence);
     }
 
@@ -793,11 +718,15 @@
         return result;
     }
 
-    std::vector<CancelBufferInput> cancelBufferInputs(numBufferRequested);
+    std::vector<CancelBufferInput> cancelBufferInputs;
+    cancelBufferInputs.reserve(numBufferRequested);
     std::vector<status_t> cancelBufferOutputs;
     for (size_t i = 0; i < numBufferRequested; i++) {
-        cancelBufferInputs[i].slot = dequeueOutput[i].slot;
-        cancelBufferInputs[i].fence = dequeueOutput[i].fence;
+        if (dequeueOutput[i].result >= 0) {
+            CancelBufferInput& input = cancelBufferInputs.emplace_back();
+            input.slot = dequeueOutput[i].slot;
+            input.fence = dequeueOutput[i].fence;
+        }
     }
 
     for (const auto& output : dequeueOutput) {
@@ -889,7 +818,7 @@
         sp<GraphicBuffer>& gbuf(mSlots[slot].buffer);
 
         if (CC_UNLIKELY(atrace_is_tag_enabled(ATRACE_TAG_GRAPHICS))) {
-            static FenceMonitor hwcReleaseThread("HWC release");
+            static gui::FenceMonitor hwcReleaseThread("HWC release");
             hwcReleaseThread.queueFence(output.fence);
         }
 
@@ -1159,7 +1088,7 @@
     mQueueBufferCondition.broadcast();
 
     if (CC_UNLIKELY(atrace_is_tag_enabled(ATRACE_TAG_GRAPHICS))) {
-        static FenceMonitor gpuCompletionThread("GPU completion");
+        static gui::FenceMonitor gpuCompletionThread("GPU completion");
         gpuCompletionThread.queueFence(fence);
     }
 }
@@ -1867,12 +1796,14 @@
     auto frameTimelineVsyncId = static_cast<int64_t>(va_arg(args, int64_t));
     auto inputEventId = static_cast<int32_t>(va_arg(args, int32_t));
     auto startTimeNanos = static_cast<int64_t>(va_arg(args, int64_t));
+    auto useForRefreshRateSelection = static_cast<bool>(va_arg(args, int32_t));
 
     ALOGV("Surface::%s", __func__);
     FrameTimelineInfo ftlInfo;
     ftlInfo.vsyncId = frameTimelineVsyncId;
     ftlInfo.inputEventId = inputEventId;
     ftlInfo.startTimeNanos = startTimeNanos;
+    ftlInfo.useForRefreshRateSelection = useForRefreshRateSelection;
     return setFrameTimelineInfo(frameNumber, ftlInfo);
 }
 
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 2f5830d..0fda358 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -54,6 +54,7 @@
 #include <ui/DynamicDisplayInfo.h>
 
 #include <android-base/thread_annotations.h>
+#include <gui/LayerStatePermissions.h>
 #include <private/gui/ComposerService.h>
 #include <private/gui/ComposerServiceAIDL.h>
 
@@ -716,11 +717,16 @@
     mListenerCallbacks = other.mListenerCallbacks;
 }
 
-void SurfaceComposerClient::Transaction::sanitize() {
+void SurfaceComposerClient::Transaction::sanitize(int pid, int uid) {
+    uint32_t permissions = LayerStatePermissions::getTransactionPermissions(pid, uid);
     for (auto & [handle, composerState] : mComposerStates) {
-        composerState.state.sanitize(0 /* permissionMask */);
+        composerState.state.sanitize(permissions);
     }
-    mInputWindowCommands.clear();
+    if (!mInputWindowCommands.empty() &&
+        (permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) == 0) {
+        ALOGE("Only privileged callers are allowed to send input commands.");
+        mInputWindowCommands.clear();
+    }
 }
 
 std::unique_ptr<SurfaceComposerClient::Transaction>
@@ -821,6 +827,15 @@
         SAFE_PARCEL(parcel->readUint64, &uncacheBuffers[i].id);
     }
 
+    count = static_cast<size_t>(parcel->readUint32());
+    if (count > parcel->dataSize()) {
+        return BAD_VALUE;
+    }
+    std::vector<uint64_t> mergedTransactionIds(count);
+    for (size_t i = 0; i < count; i++) {
+        SAFE_PARCEL(parcel->readUint64, &mergedTransactionIds[i]);
+    }
+
     // Parsing was successful. Update the object.
     mId = transactionId;
     mTransactionNestCount = transactionNestCount;
@@ -836,6 +851,7 @@
     mInputWindowCommands = inputWindowCommands;
     mApplyToken = applyToken;
     mUncacheBuffers = std::move(uncacheBuffers);
+    mMergedTransactionIds = std::move(mergedTransactionIds);
     return NO_ERROR;
 }
 
@@ -894,11 +910,16 @@
         SAFE_PARCEL(parcel->writeUint64, uncacheBuffer.id);
     }
 
+    SAFE_PARCEL(parcel->writeUint32, static_cast<uint32_t>(mMergedTransactionIds.size()));
+    for (auto mergedTransactionId : mMergedTransactionIds) {
+        SAFE_PARCEL(parcel->writeUint64, mergedTransactionId);
+    }
+
     return NO_ERROR;
 }
 
 void SurfaceComposerClient::Transaction::releaseBufferIfOverwriting(const layer_state_t& state) {
-    if (!(state.what & layer_state_t::eBufferChanged)) {
+    if (!(state.what & layer_state_t::eBufferChanged) || !state.bufferData->hasBuffer()) {
         return;
     }
 
@@ -918,6 +939,22 @@
 }
 
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::merge(Transaction&& other) {
+    while (mMergedTransactionIds.size() + other.mMergedTransactionIds.size() >
+                   MAX_MERGE_HISTORY_LENGTH - 1 &&
+           mMergedTransactionIds.size() > 0) {
+        mMergedTransactionIds.pop_back();
+    }
+    if (other.mMergedTransactionIds.size() == MAX_MERGE_HISTORY_LENGTH) {
+        mMergedTransactionIds.insert(mMergedTransactionIds.begin(),
+                                     other.mMergedTransactionIds.begin(),
+                                     other.mMergedTransactionIds.end() - 1);
+    } else if (other.mMergedTransactionIds.size() > 0u) {
+        mMergedTransactionIds.insert(mMergedTransactionIds.begin(),
+                                     other.mMergedTransactionIds.begin(),
+                                     other.mMergedTransactionIds.end());
+    }
+    mMergedTransactionIds.insert(mMergedTransactionIds.begin(), other.mId);
+
     for (auto const& [handle, composerState] : other.mComposerStates) {
         if (mComposerStates.count(handle) == 0) {
             mComposerStates[handle] = composerState;
@@ -992,12 +1029,17 @@
     mIsAutoTimestamp = true;
     clearFrameTimelineInfo(mFrameTimelineInfo);
     mApplyToken = nullptr;
+    mMergedTransactionIds.clear();
 }
 
 uint64_t SurfaceComposerClient::Transaction::getId() {
     return mId;
 }
 
+std::vector<uint64_t> SurfaceComposerClient::Transaction::getMergedTransactionIds() {
+    return mMergedTransactionIds;
+}
+
 void SurfaceComposerClient::doUncacheBufferTransaction(uint64_t cacheId) {
     sp<ISurfaceComposer> sf(ComposerService::getComposerService());
 
@@ -1008,7 +1050,7 @@
     status_t status = sf->setTransactionState(FrameTimelineInfo{}, composerStates, {},
                                               ISurfaceComposer::eOneWay,
                                               Transaction::getDefaultApplyToken(), {}, systemTime(),
-                                              true, {uncacheBuffer}, false, {}, generateId());
+                                              true, {uncacheBuffer}, false, {}, generateId(), {});
     if (status != NO_ERROR) {
         ALOGE_AND_TRACE("SurfaceComposerClient::doUncacheBufferTransaction - %s",
                         strerror(-status));
@@ -1183,7 +1225,8 @@
     sp<ISurfaceComposer> sf(ComposerService::getComposerService());
     sf->setTransactionState(mFrameTimelineInfo, composerStates, displayStates, flags, applyToken,
                             mInputWindowCommands, mDesiredPresentTime, mIsAutoTimestamp,
-                            mUncacheBuffers, hasListenerCallbacks, listenerCallbacks, mId);
+                            mUncacheBuffers, hasListenerCallbacks, listenerCallbacks, mId,
+                            mMergedTransactionIds);
     mId = generateId();
 
     // Clear the current states and flags
@@ -1642,28 +1685,25 @@
 
     releaseBufferIfOverwriting(*s);
 
-    if (buffer == nullptr) {
-        s->what &= ~layer_state_t::eBufferChanged;
-        s->bufferData = nullptr;
-        return *this;
-    }
-
     std::shared_ptr<BufferData> bufferData = std::make_shared<BufferData>();
     bufferData->buffer = buffer;
-    uint64_t frameNumber = sc->resolveFrameNumber(optFrameNumber);
-    bufferData->frameNumber = frameNumber;
-    bufferData->producerId = producerId;
-    bufferData->flags |= BufferData::BufferDataChange::frameNumberChanged;
-    if (fence) {
-        bufferData->acquireFence = *fence;
-        bufferData->flags |= BufferData::BufferDataChange::fenceChanged;
+    if (buffer) {
+        uint64_t frameNumber = sc->resolveFrameNumber(optFrameNumber);
+        bufferData->frameNumber = frameNumber;
+        bufferData->producerId = producerId;
+        bufferData->flags |= BufferData::BufferDataChange::frameNumberChanged;
+        if (fence) {
+            bufferData->acquireFence = *fence;
+            bufferData->flags |= BufferData::BufferDataChange::fenceChanged;
+        }
+        bufferData->releaseBufferEndpoint =
+                IInterface::asBinder(TransactionCompletedListener::getIInstance());
+        setReleaseBufferCallback(bufferData.get(), callback);
     }
-    bufferData->releaseBufferEndpoint =
-            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+
     if (mIsAutoTimestamp) {
         mDesiredPresentTime = systemTime();
     }
-    setReleaseBufferCallback(bufferData.get(), callback);
     s->what |= layer_state_t::eBufferChanged;
     s->bufferData = std::move(bufferData);
     registerSurfaceControlForCallback(sc);
@@ -1684,6 +1724,25 @@
     return *this;
 }
 
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::unsetBuffer(
+        const sp<SurfaceControl>& sc) {
+    layer_state_t* s = getLayerState(sc);
+    if (!s) {
+        mStatus = BAD_INDEX;
+        return *this;
+    }
+
+    if (!(s->what & layer_state_t::eBufferChanged)) {
+        return *this;
+    }
+
+    releaseBufferIfOverwriting(*s);
+
+    s->what &= ~layer_state_t::eBufferChanged;
+    s->bufferData = nullptr;
+    return *this;
+}
+
 void SurfaceComposerClient::Transaction::setReleaseBufferCallback(BufferData* bufferData,
                                                                   ReleaseBufferCallback callback) {
     if (!callback) {
@@ -1723,8 +1782,8 @@
         return *this;
     }
     s->what |= layer_state_t::eExtendedRangeBrightnessChanged;
-    s->currentSdrHdrRatio = currentBufferRatio;
-    s->desiredSdrHdrRatio = desiredRatio;
+    s->currentHdrSdrRatio = currentBufferRatio;
+    s->desiredHdrSdrRatio = desiredRatio;
 
     registerSurfaceControlForCallback(sc);
     return *this;
@@ -2223,11 +2282,13 @@
             t.vsyncId = other.vsyncId;
             t.inputEventId = other.inputEventId;
             t.startTimeNanos = other.startTimeNanos;
+            t.useForRefreshRateSelection = other.useForRefreshRateSelection;
         }
     } else if (t.vsyncId == FrameTimelineInfo::INVALID_VSYNC_ID) {
         t.vsyncId = other.vsyncId;
         t.inputEventId = other.inputEventId;
         t.startTimeNanos = other.startTimeNanos;
+        t.useForRefreshRateSelection = other.useForRefreshRateSelection;
     }
 }
 
@@ -2236,6 +2297,7 @@
     t.vsyncId = FrameTimelineInfo::INVALID_VSYNC_ID;
     t.inputEventId = os::IInputConstants::INVALID_INPUT_EVENT_ID;
     t.startTimeNanos = 0;
+    t.useForRefreshRateSelection = false;
 }
 
 SurfaceComposerClient::Transaction&
diff --git a/libs/gui/VsyncEventData.cpp b/libs/gui/VsyncEventData.cpp
index 76c60c2..8e00c2f 100644
--- a/libs/gui/VsyncEventData.cpp
+++ b/libs/gui/VsyncEventData.cpp
@@ -23,8 +23,8 @@
 
 namespace android::gui {
 
-static_assert(VsyncEventData::kFrameTimelinesLength == 7,
-              "Must update value in DisplayEventReceiver.java#FRAME_TIMELINES_LENGTH (and here)");
+static_assert(VsyncEventData::kFrameTimelinesCapacity == 7,
+              "Must update value in DisplayEventReceiver.java#FRAME_TIMELINES_CAPACITY (and here)");
 
 int64_t VsyncEventData::preferredVsyncId() const {
     return frameTimelines[preferredFrameTimelineIndex].vsyncId;
@@ -46,11 +46,15 @@
 
     SAFE_PARCEL(parcel->readInt64, &vsync.frameInterval);
 
-    uint64_t uintPreferredFrameTimelineIndex;
-    SAFE_PARCEL(parcel->readUint64, &uintPreferredFrameTimelineIndex);
+    uint32_t uintPreferredFrameTimelineIndex;
+    SAFE_PARCEL(parcel->readUint32, &uintPreferredFrameTimelineIndex);
     vsync.preferredFrameTimelineIndex = static_cast<size_t>(uintPreferredFrameTimelineIndex);
 
-    for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
+    uint32_t uintFrameTimelinesLength;
+    SAFE_PARCEL(parcel->readUint32, &uintFrameTimelinesLength);
+    vsync.frameTimelinesLength = static_cast<size_t>(uintFrameTimelinesLength);
+
+    for (size_t i = 0; i < vsync.frameTimelinesLength; i++) {
         SAFE_PARCEL(parcel->readInt64, &vsync.frameTimelines[i].vsyncId);
         SAFE_PARCEL(parcel->readInt64, &vsync.frameTimelines[i].deadlineTimestamp);
         SAFE_PARCEL(parcel->readInt64, &vsync.frameTimelines[i].expectedPresentationTime);
@@ -60,8 +64,9 @@
 }
 status_t ParcelableVsyncEventData::writeToParcel(Parcel* parcel) const {
     SAFE_PARCEL(parcel->writeInt64, vsync.frameInterval);
-    SAFE_PARCEL(parcel->writeUint64, vsync.preferredFrameTimelineIndex);
-    for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
+    SAFE_PARCEL(parcel->writeUint32, vsync.preferredFrameTimelineIndex);
+    SAFE_PARCEL(parcel->writeUint32, vsync.frameTimelinesLength);
+    for (size_t i = 0; i < vsync.frameTimelinesLength; i++) {
         SAFE_PARCEL(parcel->writeInt64, vsync.frameTimelines[i].vsyncId);
         SAFE_PARCEL(parcel->writeInt64, vsync.frameTimelines[i].deadlineTimestamp);
         SAFE_PARCEL(parcel->writeInt64, vsync.frameTimelines[i].expectedPresentationTime);
diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp
index 804ce4f..6df9ff1 100644
--- a/libs/gui/WindowInfo.cpp
+++ b/libs/gui/WindowInfo.cpp
@@ -125,6 +125,7 @@
         parcel->writeBool(replaceTouchableRegionWithCrop) ?:
         parcel->writeStrongBinder(touchableRegionCropHandle.promote()) ?:
         parcel->writeStrongBinder(windowToken);
+        parcel->writeStrongBinder(focusTransferTarget);
     // clang-format on
     return status;
 }
@@ -175,7 +176,9 @@
         parcel->read(touchableRegion) ?:
         parcel->readBool(&replaceTouchableRegionWithCrop) ?:
         parcel->readNullableStrongBinder(&touchableRegionCropHandleSp) ?:
-        parcel->readNullableStrongBinder(&windowToken);
+        parcel->readNullableStrongBinder(&windowToken) ?:
+        parcel->readNullableStrongBinder(&focusTransferTarget);
+
     // clang-format on
 
     if (status != OK) {
diff --git a/libs/gui/WindowInfosListenerReporter.cpp b/libs/gui/WindowInfosListenerReporter.cpp
index 01e865d..76e7b6e 100644
--- a/libs/gui/WindowInfosListenerReporter.cpp
+++ b/libs/gui/WindowInfosListenerReporter.cpp
@@ -17,6 +17,7 @@
 #include <android/gui/ISurfaceComposer.h>
 #include <gui/AidlStatusUtil.h>
 #include <gui/WindowInfosListenerReporter.h>
+#include "gui/WindowInfosUpdate.h"
 
 namespace android {
 
@@ -62,6 +63,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);
@@ -80,7 +85,7 @@
 }
 
 binder::Status WindowInfosListenerReporter::onWindowInfosChanged(
-        const std::vector<WindowInfo>& windowInfos, const std::vector<DisplayInfo>& displayInfos,
+        const gui::WindowInfosUpdate& update,
         const sp<IWindowInfosReportedListener>& windowInfosReportedListener) {
     std::unordered_set<sp<WindowInfosListener>, gui::SpHash<WindowInfosListener>>
             windowInfosListeners;
@@ -91,12 +96,12 @@
             windowInfosListeners.insert(listener);
         }
 
-        mLastWindowInfos = windowInfos;
-        mLastDisplayInfos = displayInfos;
+        mLastWindowInfos = update.windowInfos;
+        mLastDisplayInfos = update.displayInfos;
     }
 
     for (auto listener : windowInfosListeners) {
-        listener->onWindowInfosChanged(windowInfos, displayInfos);
+        listener->onWindowInfosChanged(update);
     }
 
     if (windowInfosReportedListener) {
diff --git a/libs/gui/WindowInfosUpdate.cpp b/libs/gui/WindowInfosUpdate.cpp
new file mode 100644
index 0000000..38ae5ef
--- /dev/null
+++ b/libs/gui/WindowInfosUpdate.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gui/WindowInfosUpdate.h>
+#include <private/gui/ParcelUtils.h>
+
+namespace android::gui {
+
+status_t WindowInfosUpdate::readFromParcel(const android::Parcel* parcel) {
+    if (parcel == nullptr) {
+        ALOGE("%s: Null parcel", __func__);
+        return BAD_VALUE;
+    }
+
+    uint32_t size;
+
+    SAFE_PARCEL(parcel->readUint32, &size);
+    windowInfos.reserve(size);
+    for (uint32_t i = 0; i < size; i++) {
+        windowInfos.push_back({});
+        SAFE_PARCEL(windowInfos.back().readFromParcel, parcel);
+    }
+
+    SAFE_PARCEL(parcel->readUint32, &size);
+    displayInfos.reserve(size);
+    for (uint32_t i = 0; i < size; i++) {
+        displayInfos.push_back({});
+        SAFE_PARCEL(displayInfos.back().readFromParcel, parcel);
+    }
+
+    SAFE_PARCEL(parcel->readInt64, &vsyncId);
+    SAFE_PARCEL(parcel->readInt64, &timestamp);
+
+    return OK;
+}
+
+status_t WindowInfosUpdate::writeToParcel(android::Parcel* parcel) const {
+    if (parcel == nullptr) {
+        ALOGE("%s: Null parcel", __func__);
+        return BAD_VALUE;
+    }
+
+    SAFE_PARCEL(parcel->writeUint32, static_cast<uint32_t>(windowInfos.size()));
+    for (auto& windowInfo : windowInfos) {
+        SAFE_PARCEL(windowInfo.writeToParcel, parcel);
+    }
+
+    SAFE_PARCEL(parcel->writeUint32, static_cast<uint32_t>(displayInfos.size()));
+    for (auto& displayInfo : displayInfos) {
+        SAFE_PARCEL(displayInfo.writeToParcel, parcel);
+    }
+
+    SAFE_PARCEL(parcel->writeInt64, vsyncId);
+    SAFE_PARCEL(parcel->writeInt64, timestamp);
+
+    return OK;
+}
+
+} // namespace android::gui
diff --git a/libs/gui/aidl/android/gui/FrameTimelineInfo.aidl b/libs/gui/aidl/android/gui/FrameTimelineInfo.aidl
index 6ffe466..6a86c6a 100644
--- a/libs/gui/aidl/android/gui/FrameTimelineInfo.aidl
+++ b/libs/gui/aidl/android/gui/FrameTimelineInfo.aidl
@@ -33,4 +33,8 @@
 
     // The current time in nanoseconds the application started to render the frame.
     long startTimeNanos = 0;
+
+    // Whether this vsyncId should be used to heuristically select the display refresh rate
+    // TODO(b/281695725): Clean this up once TextureView use setFrameRate API
+    boolean useForRefreshRateSelection = false;
 }
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/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
index aa58e2e..ec3266c 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -230,6 +230,10 @@
      */
     void captureDisplay(in DisplayCaptureArgs args, IScreenCaptureListener listener);
 
+    /**
+     * Capture the specified screen. This requires the READ_FRAME_BUFFER
+     * permission.
+     */
     void captureDisplayById(long displayId, IScreenCaptureListener listener);
 
     /**
diff --git a/libs/gui/android/gui/FocusRequest.aidl b/libs/gui/android/gui/FocusRequest.aidl
index b13c600..62d1b68 100644
--- a/libs/gui/android/gui/FocusRequest.aidl
+++ b/libs/gui/android/gui/FocusRequest.aidl
@@ -24,15 +24,6 @@
     @nullable IBinder token;
     @utf8InCpp String windowName;
     /**
-     * The token that the caller expects currently to be focused. If the
-     * specified token does not match the currently focused window, this request will be dropped.
-     * If the specified focused token matches the currently focused window, the call will succeed.
-     * Set this to "null" if this call should succeed no matter what the currently focused token
-     * is.
-     */
-    @nullable IBinder focusedToken;
-    @utf8InCpp String focusedWindowName;
-    /**
      * SYSTEM_TIME_MONOTONIC timestamp in nanos set by the client (wm) when requesting the focus
      * change. This determines which request gets precedence if there is a focus change request
      * from another source such as pointer down.
diff --git a/libs/gui/android/gui/IWindowInfosListener.aidl b/libs/gui/android/gui/IWindowInfosListener.aidl
index a5b2762..400229d 100644
--- a/libs/gui/android/gui/IWindowInfosListener.aidl
+++ b/libs/gui/android/gui/IWindowInfosListener.aidl
@@ -16,12 +16,11 @@
 
 package android.gui;
 
-import android.gui.DisplayInfo;
 import android.gui.IWindowInfosReportedListener;
-import android.gui.WindowInfo;
+import android.gui.WindowInfosUpdate;
 
 /** @hide */
-oneway interface IWindowInfosListener
-{
-    void onWindowInfosChanged(in WindowInfo[] windowInfos, in DisplayInfo[] displayInfos, in @nullable IWindowInfosReportedListener windowInfosReportedListener);
+oneway interface IWindowInfosListener {
+    void onWindowInfosChanged(
+        in WindowInfosUpdate update, in @nullable IWindowInfosReportedListener windowInfosReportedListener);
 }
diff --git a/libs/gui/android/gui/WindowInfosUpdate.aidl b/libs/gui/android/gui/WindowInfosUpdate.aidl
new file mode 100644
index 0000000..0c6109d
--- /dev/null
+++ b/libs/gui/android/gui/WindowInfosUpdate.aidl
@@ -0,0 +1,22 @@
+/*
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.gui;
+
+import android.gui.DisplayInfo;
+import android.gui.WindowInfo;
+
+parcelable WindowInfosUpdate cpp_header "gui/WindowInfosUpdate.h";
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/fuzzer/libgui_displayEvent_fuzzer.cpp b/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp
index 6d5ae49..6e4f074 100644
--- a/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp
+++ b/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp
@@ -51,7 +51,7 @@
             event.vsync.count = fdp->ConsumeIntegral<uint32_t>();
             event.vsync.vsyncData.frameInterval = fdp->ConsumeIntegral<uint64_t>();
             event.vsync.vsyncData.preferredFrameTimelineIndex = fdp->ConsumeIntegral<uint32_t>();
-            for (size_t idx = 0; idx < gui::VsyncEventData::kFrameTimelinesLength; ++idx) {
+            for (size_t idx = 0; idx < gui::VsyncEventData::kFrameTimelinesCapacity; ++idx) {
                 event.vsync.vsyncData.frameTimelines[idx].vsyncId = fdp->ConsumeIntegral<int64_t>();
                 event.vsync.vsyncData.frameTimelines[idx].deadlineTimestamp =
                         fdp->ConsumeIntegral<uint64_t>();
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index b9e0647..a49a859 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);
 
@@ -95,9 +97,10 @@
     void releaseBufferCallbackLocked(const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
                                      std::optional<uint32_t> currentMaxAcquiredBufferCount,
                                      bool fakeRelease) REQUIRES(mMutex);
-    void syncNextTransaction(std::function<void(SurfaceComposerClient::Transaction*)> callback,
+    bool syncNextTransaction(std::function<void(SurfaceComposerClient::Transaction*)> callback,
                              bool acquireSingleBuffer = true);
     void stopContinuousSyncTransaction();
+    void clearSyncTransaction();
 
     void mergeWithNextTransaction(SurfaceComposerClient::Transaction* t, uint64_t frameNumber);
     void applyPendingTransactions(uint64_t frameNumber);
@@ -123,6 +126,7 @@
 
 private:
     friend class BLASTBufferQueueHelper;
+    friend class BBQBufferQueueProducer;
 
     // can't be copied
     BLASTBufferQueue& operator = (const BLASTBufferQueue& rhs);
@@ -130,6 +134,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/DisplayCaptureArgs.h b/libs/gui/include/gui/DisplayCaptureArgs.h
index c826c17..2676e0a 100644
--- a/libs/gui/include/gui/DisplayCaptureArgs.h
+++ b/libs/gui/include/gui/DisplayCaptureArgs.h
@@ -22,9 +22,11 @@
 #include <binder/IBinder.h>
 #include <binder/Parcel.h>
 #include <binder/Parcelable.h>
+#include <gui/SpHash.h>
 #include <ui/GraphicTypes.h>
 #include <ui/PixelFormat.h>
 #include <ui/Rect.h>
+#include <unordered_set>
 
 namespace android::gui {
 
@@ -39,7 +41,7 @@
     bool captureSecureLayers{false};
     int32_t uid{UNSET_UID};
     // Force capture to be in a color space. If the value is ui::Dataspace::UNKNOWN, the captured
-    // result will be in the display's colorspace.
+    // result will be in a colorspace appropriate for capturing the display contents
     // The display may use non-RGB dataspace (ex. displayP3) that could cause pixel data could be
     // different from SRGB (byte per color), and failed when checking colors in tests.
     // NOTE: In normal cases, we want the screen to be captured in display's colorspace.
@@ -55,6 +57,17 @@
 
     bool grayscale = false;
 
+    std::unordered_set<sp<IBinder>, SpHash<IBinder>> excludeHandles;
+
+    // Hint that the caller will use the screenshot animation as part of a transition animation.
+    // The canonical example would be screen rotation - in such a case any color shift in the
+    // screenshot is a detractor so composition in the display's colorspace is required.
+    // Otherwise, the system may choose a colorspace that is more appropriate for use-cases
+    // such as file encoding or for blending HDR content into an ap's UI, where the display's
+    // exact colorspace is not an appropriate intermediate result.
+    // Note that if the caller is requesting a specific dataspace, this hint does nothing.
+    bool hintForSeamlessTransition = false;
+
     virtual status_t writeToParcel(Parcel* output) const;
     virtual status_t readFromParcel(const Parcel* input);
 };
diff --git a/libs/gui/include/gui/FenceMonitor.h b/libs/gui/include/gui/FenceMonitor.h
new file mode 100644
index 0000000..62cedde
--- /dev/null
+++ b/libs/gui/include/gui/FenceMonitor.h
@@ -0,0 +1,44 @@
+/*
+ * 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 <cstdint>
+#include <deque>
+#include <mutex>
+
+#include <ui/Fence.h>
+
+namespace android::gui {
+
+class FenceMonitor {
+public:
+    explicit FenceMonitor(const char* name);
+    void queueFence(const sp<Fence>& fence);
+
+private:
+    void loop();
+    void threadLoop();
+
+    const char* mName;
+    uint32_t mFencesQueued;
+    uint32_t mFencesSignaled;
+    std::deque<sp<Fence>> mQueue;
+    std::condition_variable mCondition;
+    std::mutex mMutex;
+};
+
+} // namespace android::gui
\ No newline at end of file
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/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index 1e67225..7c150d5 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -113,10 +113,10 @@
     virtual status_t setTransactionState(
             const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
             const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
-            const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
+            InputWindowCommands inputWindowCommands, int64_t desiredPresentTime,
             bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffer,
             bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks,
-            uint64_t transactionId) = 0;
+            uint64_t transactionId, const std::vector<uint64_t>& mergedTransactionIds) = 0;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/libs/gui/include/gui/LayerCaptureArgs.h b/libs/gui/include/gui/LayerCaptureArgs.h
index 05ff9d5..fae2bcc 100644
--- a/libs/gui/include/gui/LayerCaptureArgs.h
+++ b/libs/gui/include/gui/LayerCaptureArgs.h
@@ -20,14 +20,11 @@
 #include <sys/types.h>
 
 #include <gui/DisplayCaptureArgs.h>
-#include <gui/SpHash.h>
-#include <unordered_set>
 
 namespace android::gui {
 
 struct LayerCaptureArgs : CaptureArgs {
     sp<IBinder> layerHandle;
-    std::unordered_set<sp<IBinder>, SpHash<IBinder>> excludeHandles;
     bool childrenOnly{false};
 
     status_t writeToParcel(Parcel* output) const override;
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 6e3be5c..a6f503e 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -233,9 +233,10 @@
 
     // Geometry updates.
     static constexpr uint64_t GEOMETRY_CHANGES = layer_state_t::eBufferCropChanged |
-            layer_state_t::eBufferTransformChanged | layer_state_t::eCropChanged |
-            layer_state_t::eDestinationFrameChanged | layer_state_t::eMatrixChanged |
-            layer_state_t::ePositionChanged | layer_state_t::eTransformToDisplayInverseChanged |
+            layer_state_t::eBufferTransformChanged | layer_state_t::eCornerRadiusChanged |
+            layer_state_t::eCropChanged | layer_state_t::eDestinationFrameChanged |
+            layer_state_t::eMatrixChanged | layer_state_t::ePositionChanged |
+            layer_state_t::eTransformToDisplayInverseChanged |
             layer_state_t::eTransparentRegionChanged;
 
     // Buffer and related updates.
@@ -389,8 +390,8 @@
     gui::DropInputMode dropInputMode;
 
     bool dimmingEnabled;
-    float currentSdrHdrRatio = 1.f;
-    float desiredSdrHdrRatio = 1.f;
+    float currentHdrSdrRatio = 1.f;
+    float desiredHdrSdrRatio = 1.f;
 
     gui::CachingHint cachingHint = gui::CachingHint::Enabled;
 
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.cpp b/libs/gui/include/gui/LayerStatePermissions.h
similarity index 60%
copy from services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.cpp
copy to libs/gui/include/gui/LayerStatePermissions.h
index 5049b1d..a90f30c 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.cpp
+++ b/libs/gui/include/gui/LayerStatePermissions.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,13 +14,16 @@
  * limitations under the License.
  */
 
-#include "MockAidlPowerHalWrapper.h"
-#include "MockIPower.h"
+#include <stdint.h>
+#include <string>
+#include <unordered_map>
 
-namespace android::Hwc2::mock {
+namespace android {
+class LayerStatePermissions {
+public:
+    static uint32_t getTransactionPermissions(int pid, int uid);
 
-MockAidlPowerHalWrapper::MockAidlPowerHalWrapper()
-      : AidlPowerHalWrapper(sp<testing::NiceMock<MockIPower>>::make()){};
-MockAidlPowerHalWrapper::~MockAidlPowerHalWrapper() = default;
-
-} // namespace android::Hwc2::mock
+private:
+    static std::unordered_map<std::string, int> mPermissionMap;
+};
+} // namespace android
\ No newline at end of file
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index d431b43..fb57f63 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -419,6 +419,11 @@
                 mListenerCallbacks;
         std::vector<client_cache_t> mUncacheBuffers;
 
+        // We keep track of the last MAX_MERGE_HISTORY_LENGTH merged transaction ids.
+        // Ordered most recently merged to least recently merged.
+        static const size_t MAX_MERGE_HISTORY_LENGTH = 10u;
+        std::vector<uint64_t> mMergedTransactionIds;
+
         uint64_t mId;
 
         uint32_t mTransactionNestCount = 0;
@@ -482,6 +487,8 @@
         // The id is updated every time the transaction is applied.
         uint64_t getId();
 
+        std::vector<uint64_t> getMergedTransactionIds();
+
         status_t apply(bool synchronous = false, bool oneWay = false);
         // Merge another transaction in to this one, clearing other
         // as if it had been applied.
@@ -541,6 +548,7 @@
                                const std::optional<sp<Fence>>& fence = std::nullopt,
                                const std::optional<uint64_t>& frameNumber = std::nullopt,
                                uint32_t producerId = 0, ReleaseBufferCallback callback = nullptr);
+        Transaction& unsetBuffer(const sp<SurfaceControl>& sc);
         std::shared_ptr<BufferData> getAndClearBuffer(const sp<SurfaceControl>& sc);
 
         /**
@@ -743,7 +751,7 @@
          *
          * TODO (b/213644870): Remove all permissioned things from Transaction
          */
-        void sanitize();
+        void sanitize(int pid, int uid);
 
         static sp<IBinder> getDefaultApplyToken();
         static void setDefaultApplyToken(sp<IBinder> applyToken);
diff --git a/libs/gui/include/gui/VsyncEventData.h b/libs/gui/include/gui/VsyncEventData.h
index dfdae21..b40a840 100644
--- a/libs/gui/include/gui/VsyncEventData.h
+++ b/libs/gui/include/gui/VsyncEventData.h
@@ -24,8 +24,8 @@
 // Plain Old Data (POD) vsync data structure. For example, it can be easily used in the
 // DisplayEventReceiver::Event union.
 struct VsyncEventData {
-    // Max amount of frame timelines is arbitrarily set to be reasonable.
-    static constexpr int64_t kFrameTimelinesLength = 7;
+    // Max capacity of frame timelines is arbitrarily set to be reasonable.
+    static constexpr int64_t kFrameTimelinesCapacity = 7;
 
     // The current frame interval in ns when this frame was scheduled.
     int64_t frameInterval;
@@ -33,6 +33,9 @@
     // Index into the frameTimelines that represents the platform's preferred frame timeline.
     uint32_t preferredFrameTimelineIndex;
 
+    // Size of frame timelines provided by the platform; max is kFrameTimelinesCapacity.
+    uint32_t frameTimelinesLength;
+
     struct alignas(8) FrameTimeline {
         // The Vsync Id corresponsing to this vsync event. This will be used to
         // populate ISurfaceComposer::setFrameTimelineVsync and
@@ -45,7 +48,7 @@
 
         // The anticipated Vsync presentation time in nanos.
         int64_t expectedPresentationTime;
-    } frameTimelines[kFrameTimelinesLength]; // Sorted possible frame timelines.
+    } frameTimelines[kFrameTimelinesCapacity]; // Sorted possible frame timelines.
 
     // Gets the preferred frame timeline's vsync ID.
     int64_t preferredVsyncId() const;
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index b01a3db..70b2ee8 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -236,6 +236,11 @@
     Type layoutParamsType = Type::UNKNOWN;
     ftl::Flags<Flag> layoutParamsFlags;
 
+    // The input token for the window to which focus should be transferred when this input window
+    // can be successfully focused. If null, this input window will not transfer its focus to
+    // any other window.
+    sp<IBinder> focusTransferTarget;
+
     void setInputConfig(ftl::Flags<InputConfig> config, bool value);
 
     void addTouchableRegion(const Rect& region);
diff --git a/libs/gui/include/gui/WindowInfosListener.h b/libs/gui/include/gui/WindowInfosListener.h
index a18a498..02c8eb5 100644
--- a/libs/gui/include/gui/WindowInfosListener.h
+++ b/libs/gui/include/gui/WindowInfosListener.h
@@ -16,15 +16,13 @@
 
 #pragma once
 
-#include <gui/DisplayInfo.h>
-#include <gui/WindowInfo.h>
+#include <gui/WindowInfosUpdate.h>
 #include <utils/RefBase.h>
 
 namespace android::gui {
 
 class WindowInfosListener : public virtual RefBase {
 public:
-    virtual void onWindowInfosChanged(const std::vector<WindowInfo>&,
-                                      const std::vector<DisplayInfo>&) = 0;
+    virtual void onWindowInfosChanged(const WindowInfosUpdate& update) = 0;
 };
-} // namespace android::gui
\ No newline at end of file
+} // namespace android::gui
diff --git a/libs/gui/include/gui/WindowInfosListenerReporter.h b/libs/gui/include/gui/WindowInfosListenerReporter.h
index 2754442..38cb108 100644
--- a/libs/gui/include/gui/WindowInfosListenerReporter.h
+++ b/libs/gui/include/gui/WindowInfosListenerReporter.h
@@ -22,6 +22,7 @@
 #include <binder/IBinder.h>
 #include <gui/SpHash.h>
 #include <gui/WindowInfosListener.h>
+#include <gui/WindowInfosUpdate.h>
 #include <unordered_set>
 
 namespace android {
@@ -29,8 +30,7 @@
 class WindowInfosListenerReporter : public gui::BnWindowInfosListener {
 public:
     static sp<WindowInfosListenerReporter> getInstance();
-    binder::Status onWindowInfosChanged(const std::vector<gui::WindowInfo>&,
-                                        const std::vector<gui::DisplayInfo>&,
+    binder::Status onWindowInfosChanged(const gui::WindowInfosUpdate& update,
                                         const sp<gui::IWindowInfosReportedListener>&) override;
     status_t addWindowInfosListener(
             const sp<gui::WindowInfosListener>& windowInfosListener,
diff --git a/libs/gui/include/gui/WindowInfosUpdate.h b/libs/gui/include/gui/WindowInfosUpdate.h
new file mode 100644
index 0000000..2ca59fb
--- /dev/null
+++ b/libs/gui/include/gui/WindowInfosUpdate.h
@@ -0,0 +1,44 @@
+/*
+ * 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 <binder/Parcelable.h>
+#include <gui/DisplayInfo.h>
+#include <gui/WindowInfo.h>
+
+namespace android::gui {
+
+struct WindowInfosUpdate : public Parcelable {
+    WindowInfosUpdate() {}
+
+    WindowInfosUpdate(std::vector<WindowInfo> windowInfos, std::vector<DisplayInfo> displayInfos,
+                      int64_t vsyncId, int64_t timestamp)
+          : windowInfos(std::move(windowInfos)),
+            displayInfos(std::move(displayInfos)),
+            vsyncId(vsyncId),
+            timestamp(timestamp) {}
+
+    std::vector<WindowInfo> windowInfos;
+    std::vector<DisplayInfo> displayInfos;
+    int64_t vsyncId;
+    int64_t timestamp;
+
+    status_t writeToParcel(android::Parcel*) const override;
+    status_t readFromParcel(const android::Parcel*) override;
+};
+
+} // namespace android::gui
diff --git a/libs/gui/include/gui/test/CallbackUtils.h b/libs/gui/include/gui/test/CallbackUtils.h
index 08785b4..1c900e9 100644
--- a/libs/gui/include/gui/test/CallbackUtils.h
+++ b/libs/gui/include/gui/test/CallbackUtils.h
@@ -51,6 +51,7 @@
     enum Buffer {
         NOT_ACQUIRED = 0,
         ACQUIRED,
+        ACQUIRED_NULL,
     };
 
     enum PreviousBuffer {
@@ -133,17 +134,28 @@
               : mBufferResult(bufferResult), mPreviousBufferResult(previousBufferResult) {}
 
         void verifySurfaceControlStats(const SurfaceControlStats& surfaceControlStats,
-                                       nsecs_t latchTime) const {
+                                       nsecs_t /* latchTime */) const {
             const auto& [surfaceControl, latch, acquireTimeOrFence, presentFence,
                          previousReleaseFence, transformHint, frameEvents, ignore] =
-                surfaceControlStats;
+                    surfaceControlStats;
 
-            ASSERT_TRUE(std::holds_alternative<nsecs_t>(acquireTimeOrFence));
-            ASSERT_EQ(std::get<nsecs_t>(acquireTimeOrFence) > 0,
-                      mBufferResult == ExpectedResult::Buffer::ACQUIRED)
-                    << "bad acquire time";
-            ASSERT_LE(std::get<nsecs_t>(acquireTimeOrFence), latchTime)
-                    << "acquire time should be <= latch time";
+            nsecs_t acquireTime = -1;
+            if (std::holds_alternative<nsecs_t>(acquireTimeOrFence)) {
+                acquireTime = std::get<nsecs_t>(acquireTimeOrFence);
+            } else {
+                auto fence = std::get<sp<Fence>>(acquireTimeOrFence);
+                if (fence) {
+                    ASSERT_EQ(fence->wait(3000), NO_ERROR);
+                    acquireTime = fence->getSignalTime();
+                }
+            }
+
+            if (mBufferResult == ExpectedResult::Buffer::ACQUIRED) {
+                ASSERT_GT(acquireTime, 0) << "acquire time should be valid";
+            } else {
+                ASSERT_LE(acquireTime, 0) << "acquire time should not be valid";
+            }
+            ASSERT_EQ(acquireTime > 0, mBufferResult == ExpectedResult::Buffer::ACQUIRED);
 
             if (mPreviousBufferResult == ExpectedResult::PreviousBuffer::RELEASED) {
                 ASSERT_NE(previousReleaseFence, nullptr)
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/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp
index cf2593d..a3ad680 100644
--- a/libs/gui/tests/BLASTBufferQueue_test.cpp
+++ b/libs/gui/tests/BLASTBufferQueue_test.cpp
@@ -32,6 +32,7 @@
 #include <private/gui/ComposerService.h>
 #include <private/gui/ComposerServiceAIDL.h>
 #include <ui/DisplayMode.h>
+#include <ui/DisplayState.h>
 #include <ui/GraphicBuffer.h>
 #include <ui/GraphicTypes.h>
 #include <ui/Transform.h>
@@ -116,15 +117,17 @@
         mBlastBufferQueueAdapter->syncNextTransaction(callback, acquireSingleBuffer);
     }
 
-    void syncNextTransaction(std::function<void(Transaction*)> callback,
+    bool syncNextTransaction(std::function<void(Transaction*)> callback,
                              bool acquireSingleBuffer = true) {
-        mBlastBufferQueueAdapter->syncNextTransaction(callback, acquireSingleBuffer);
+        return mBlastBufferQueueAdapter->syncNextTransaction(callback, acquireSingleBuffer);
     }
 
     void stopContinuousSyncTransaction() {
         mBlastBufferQueueAdapter->stopContinuousSyncTransaction();
     }
 
+    void clearSyncTransaction() { mBlastBufferQueueAdapter->clearSyncTransaction(); }
+
     int getWidth() { return mBlastBufferQueueAdapter->mSize.width; }
 
     int getHeight() { return mBlastBufferQueueAdapter->mSize.height; }
@@ -176,13 +179,13 @@
     BLASTBufferQueueTest() {
         const ::testing::TestInfo* const testInfo =
                 ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGV("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name());
+        ALOGD("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name());
     }
 
     ~BLASTBufferQueueTest() {
         const ::testing::TestInfo* const testInfo =
                 ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGV("End test:   %s.%s", testInfo->test_case_name(), testInfo->name());
+        ALOGD("End test:   %s.%s", testInfo->test_case_name(), testInfo->name());
     }
 
     void SetUp() {
@@ -198,11 +201,13 @@
         t.apply();
         t.clear();
 
-        ui::DisplayMode mode;
-        ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(mDisplayToken, &mode));
-        const ui::Size& resolution = mode.resolution;
+        ui::DisplayState displayState;
+        ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayState(mDisplayToken, &displayState));
+        const ui::Size& resolution = displayState.layerStackSpaceRect;
         mDisplayWidth = resolution.getWidth();
         mDisplayHeight = resolution.getHeight();
+        ALOGD("Display: %dx%d orientation:%d", mDisplayWidth, mDisplayHeight,
+              displayState.orientation);
 
         mSurfaceControl = mClient->createSurface(String8("TestSurface"), mDisplayWidth,
                                                  mDisplayHeight, PIXEL_FORMAT_RGBA_8888,
@@ -1108,7 +1113,11 @@
     ASSERT_NE(nullptr, adapter.getTransactionReadyCallback());
 
     auto callback2 = [](Transaction*) {};
-    adapter.syncNextTransaction(callback2);
+    ASSERT_FALSE(adapter.syncNextTransaction(callback2));
+
+    sp<IGraphicBufferProducer> igbProducer;
+    setUpProducer(adapter, igbProducer);
+    queueBuffer(igbProducer, 0, 255, 0, 0);
 
     std::unique_lock<std::mutex> lock(mutex);
     if (!receivedCallback) {
@@ -1120,6 +1129,37 @@
     ASSERT_TRUE(receivedCallback);
 }
 
+TEST_F(BLASTBufferQueueTest, ClearSyncTransaction) {
+    std::mutex mutex;
+    std::condition_variable callbackReceivedCv;
+    bool receivedCallback = false;
+
+    BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+    ASSERT_EQ(nullptr, adapter.getTransactionReadyCallback());
+    auto callback = [&](Transaction*) {
+        std::unique_lock<std::mutex> lock(mutex);
+        receivedCallback = true;
+        callbackReceivedCv.notify_one();
+    };
+    adapter.syncNextTransaction(callback);
+    ASSERT_NE(nullptr, adapter.getTransactionReadyCallback());
+
+    adapter.clearSyncTransaction();
+
+    sp<IGraphicBufferProducer> igbProducer;
+    setUpProducer(adapter, igbProducer);
+    queueBuffer(igbProducer, 0, 255, 0, 0);
+
+    std::unique_lock<std::mutex> lock(mutex);
+    if (!receivedCallback) {
+        ASSERT_EQ(callbackReceivedCv.wait_for(lock, std::chrono::seconds(3)),
+                  std::cv_status::timeout)
+                << "did not receive callback";
+    }
+
+    ASSERT_FALSE(receivedCallback);
+}
+
 TEST_F(BLASTBufferQueueTest, SyncNextTransactionDropBuffer) {
     uint8_t r = 255;
     uint8_t g = 0;
diff --git a/libs/gui/tests/BufferItemConsumer_test.cpp b/libs/gui/tests/BufferItemConsumer_test.cpp
index fc6551c..6880678 100644
--- a/libs/gui/tests/BufferItemConsumer_test.cpp
+++ b/libs/gui/tests/BufferItemConsumer_test.cpp
@@ -68,7 +68,7 @@
     void HandleBufferFreed() {
         std::lock_guard<std::mutex> lock(mMutex);
         mFreedBufferCount++;
-        ALOGV("HandleBufferFreed, mFreedBufferCount=%d", mFreedBufferCount);
+        ALOGD("HandleBufferFreed, mFreedBufferCount=%d", mFreedBufferCount);
     }
 
     void DequeueBuffer(int* outSlot) {
@@ -80,7 +80,7 @@
                                                 nullptr, nullptr);
         ASSERT_GE(ret, 0);
 
-        ALOGV("dequeueBuffer: slot=%d", slot);
+        ALOGD("dequeueBuffer: slot=%d", slot);
         if (ret & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) {
             ret = mProducer->requestBuffer(slot, &mBuffers[slot]);
             ASSERT_EQ(NO_ERROR, ret);
@@ -89,7 +89,7 @@
     }
 
     void QueueBuffer(int slot) {
-        ALOGV("enqueueBuffer: slot=%d", slot);
+        ALOGD("enqueueBuffer: slot=%d", slot);
         IGraphicBufferProducer::QueueBufferInput bufferInput(
             0ULL, true, HAL_DATASPACE_UNKNOWN, Rect::INVALID_RECT,
             NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, Fence::NO_FENCE);
@@ -104,12 +104,12 @@
         status_t ret = mBIC->acquireBuffer(&buffer, 0, false);
         ASSERT_EQ(NO_ERROR, ret);
 
-        ALOGV("acquireBuffer: slot=%d", buffer.mSlot);
+        ALOGD("acquireBuffer: slot=%d", buffer.mSlot);
         *outSlot = buffer.mSlot;
     }
 
     void ReleaseBuffer(int slot) {
-        ALOGV("releaseBuffer: slot=%d", slot);
+        ALOGD("releaseBuffer: slot=%d", slot);
         BufferItem buffer;
         buffer.mSlot = slot;
         buffer.mGraphicBuffer = mBuffers[slot];
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index d1208ee..2f1fd3e 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -49,14 +49,14 @@
     BufferQueueTest() {
         const ::testing::TestInfo* const testInfo =
             ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGV("Begin test: %s.%s", testInfo->test_case_name(),
+        ALOGD("Begin test: %s.%s", testInfo->test_case_name(),
                 testInfo->name());
     }
 
     ~BufferQueueTest() {
         const ::testing::TestInfo* const testInfo =
             ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGV("End test:   %s.%s", testInfo->test_case_name(),
+        ALOGD("End test:   %s.%s", testInfo->test_case_name(),
                 testInfo->name());
     }
 
diff --git a/libs/gui/tests/CpuConsumer_test.cpp b/libs/gui/tests/CpuConsumer_test.cpp
index 00e32d9..0a14afa 100644
--- a/libs/gui/tests/CpuConsumer_test.cpp
+++ b/libs/gui/tests/CpuConsumer_test.cpp
@@ -62,7 +62,7 @@
         const ::testing::TestInfo* const test_info =
                 ::testing::UnitTest::GetInstance()->current_test_info();
         CpuConsumerTestParams params = GetParam();
-        ALOGV("** Starting test %s (%d x %d, %d, 0x%x)",
+        ALOGD("** Starting test %s (%d x %d, %d, 0x%x)",
                 test_info->name(),
                 params.width, params.height,
                 params.maxLockedBuffers, params.format);
@@ -582,7 +582,7 @@
     uint32_t stride[numInQueue];
 
     for (int i = 0; i < numInQueue; i++) {
-        ALOGV("Producing frame %d", i);
+        ALOGD("Producing frame %d", i);
         ASSERT_NO_FATAL_FAILURE(produceOneFrame(mANW, params, time[i],
                         &stride[i]));
     }
@@ -590,7 +590,7 @@
     // Consume
 
     for (int i = 0; i < numInQueue; i++) {
-        ALOGV("Consuming frame %d", i);
+        ALOGD("Consuming frame %d", i);
         CpuConsumer::LockedBuffer b;
         err = mCC->lockNextBuffer(&b);
         ASSERT_NO_ERROR(err, "getNextBuffer error: ");
@@ -624,7 +624,7 @@
     uint32_t stride;
 
     for (int i = 0; i < params.maxLockedBuffers + 1; i++) {
-        ALOGV("Producing frame %d", i);
+        ALOGD("Producing frame %d", i);
         ASSERT_NO_FATAL_FAILURE(produceOneFrame(mANW, params, time,
                         &stride));
     }
@@ -633,7 +633,7 @@
 
     std::vector<CpuConsumer::LockedBuffer> b(params.maxLockedBuffers);
     for (int i = 0; i < params.maxLockedBuffers; i++) {
-        ALOGV("Locking frame %d", i);
+        ALOGD("Locking frame %d", i);
         err = mCC->lockNextBuffer(&b[i]);
         ASSERT_NO_ERROR(err, "getNextBuffer error: ");
 
@@ -647,16 +647,16 @@
         checkAnyBuffer(b[i], GetParam().format);
     }
 
-    ALOGV("Locking frame %d (too many)", params.maxLockedBuffers);
+    ALOGD("Locking frame %d (too many)", params.maxLockedBuffers);
     CpuConsumer::LockedBuffer bTooMuch;
     err = mCC->lockNextBuffer(&bTooMuch);
     ASSERT_TRUE(err == NOT_ENOUGH_DATA) << "Allowing too many locks";
 
-    ALOGV("Unlocking frame 0");
+    ALOGD("Unlocking frame 0");
     err = mCC->unlockBuffer(b[0]);
     ASSERT_NO_ERROR(err, "Could not unlock buffer 0: ");
 
-    ALOGV("Locking frame %d (should work now)", params.maxLockedBuffers);
+    ALOGD("Locking frame %d (should work now)", params.maxLockedBuffers);
     err = mCC->lockNextBuffer(&bTooMuch);
     ASSERT_NO_ERROR(err, "Did not allow new lock after unlock");
 
@@ -669,11 +669,11 @@
 
     checkAnyBuffer(bTooMuch, GetParam().format);
 
-    ALOGV("Unlocking extra buffer");
+    ALOGD("Unlocking extra buffer");
     err = mCC->unlockBuffer(bTooMuch);
     ASSERT_NO_ERROR(err, "Could not unlock extra buffer: ");
 
-    ALOGV("Locking frame %d (no more available)", params.maxLockedBuffers + 1);
+    ALOGD("Locking frame %d (no more available)", params.maxLockedBuffers + 1);
     err = mCC->lockNextBuffer(&b[0]);
     ASSERT_EQ(BAD_VALUE, err) << "Not out of buffers somehow";
 
diff --git a/libs/gui/tests/DisplayEventStructLayout_test.cpp b/libs/gui/tests/DisplayEventStructLayout_test.cpp
index da88463..3949d70 100644
--- a/libs/gui/tests/DisplayEventStructLayout_test.cpp
+++ b/libs/gui/tests/DisplayEventStructLayout_test.cpp
@@ -35,6 +35,7 @@
     CHECK_OFFSET(DisplayEventReceiver::Event::VSync, count, 0);
     CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameInterval, 8);
     CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.preferredFrameTimelineIndex, 16);
+    CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelinesLength, 20);
     CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines, 24);
     CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines[0].vsyncId, 24);
     CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines[0].deadlineTimestamp,
@@ -44,16 +45,16 @@
     // Also test the offsets of the last frame timeline. A loop is not used because the non-const
     // index cannot be used in static_assert.
     const int lastFrameTimelineOffset = /* Start of array */ 24 +
-            (VsyncEventData::kFrameTimelinesLength - 1) * /* Size of FrameTimeline */ 24;
+            (VsyncEventData::kFrameTimelinesCapacity - 1) * /* Size of FrameTimeline */ 24;
     CHECK_OFFSET(DisplayEventReceiver::Event::VSync,
-                 vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesLength - 1].vsyncId,
+                 vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesCapacity - 1].vsyncId,
                  lastFrameTimelineOffset);
     CHECK_OFFSET(DisplayEventReceiver::Event::VSync,
-                 vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesLength - 1]
+                 vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesCapacity - 1]
                          .deadlineTimestamp,
                  lastFrameTimelineOffset + 8);
     CHECK_OFFSET(DisplayEventReceiver::Event::VSync,
-                 vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesLength - 1]
+                 vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesCapacity - 1]
                          .expectedPresentationTime,
                  lastFrameTimelineOffset + 16);
 
diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp
index 3344e0b..4ec7a06 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -164,7 +164,7 @@
     void assertFocusChange(bool hasFocus) {
         InputEvent *ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
-        ASSERT_EQ(AINPUT_EVENT_TYPE_FOCUS, ev->getType());
+        ASSERT_EQ(InputEventType::FOCUS, ev->getType());
         FocusEvent *focusEvent = static_cast<FocusEvent *>(ev);
         EXPECT_EQ(hasFocus, focusEvent->getHasFocus());
     }
@@ -172,7 +172,7 @@
     void expectTap(int x, int y) {
         InputEvent* ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
-        ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, ev->getType());
+        ASSERT_EQ(InputEventType::MOTION, ev->getType());
         MotionEvent* mev = static_cast<MotionEvent*>(ev);
         EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, mev->getAction());
         EXPECT_EQ(x, mev->getX(0));
@@ -181,7 +181,7 @@
 
         ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
-        ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, ev->getType());
+        ASSERT_EQ(InputEventType::MOTION, ev->getType());
         mev = static_cast<MotionEvent*>(ev);
         EXPECT_EQ(AMOTION_EVENT_ACTION_UP, mev->getAction());
         EXPECT_EQ(0, mev->getFlags() & VERIFIED_MOTION_EVENT_FLAGS);
@@ -190,7 +190,7 @@
     void expectTapWithFlag(int x, int y, int32_t flags) {
         InputEvent *ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
-        ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, ev->getType());
+        ASSERT_EQ(InputEventType::MOTION, ev->getType());
         MotionEvent *mev = static_cast<MotionEvent *>(ev);
         EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, mev->getAction());
         EXPECT_EQ(x, mev->getX(0));
@@ -199,7 +199,7 @@
 
         ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
-        ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, ev->getType());
+        ASSERT_EQ(InputEventType::MOTION, ev->getType());
         mev = static_cast<MotionEvent *>(ev);
         EXPECT_EQ(AMOTION_EVENT_ACTION_UP, mev->getAction());
         EXPECT_EQ(flags, mev->getFlags() & flags);
@@ -208,7 +208,7 @@
     void expectTapInDisplayCoordinates(int displayX, int displayY) {
         InputEvent *ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
-        ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, ev->getType());
+        ASSERT_EQ(InputEventType::MOTION, ev->getType());
         MotionEvent *mev = static_cast<MotionEvent *>(ev);
         EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, mev->getAction());
         const PointerCoords &coords = *mev->getRawPointerCoords(0 /*pointerIndex*/);
@@ -218,7 +218,7 @@
 
         ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
-        ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, ev->getType());
+        ASSERT_EQ(InputEventType::MOTION, ev->getType());
         mev = static_cast<MotionEvent *>(ev);
         EXPECT_EQ(AMOTION_EVENT_ACTION_UP, mev->getAction());
         EXPECT_EQ(0, mev->getFlags() & VERIFIED_MOTION_EVENT_FLAGS);
@@ -227,7 +227,7 @@
     void expectKey(uint32_t keycode) {
         InputEvent *ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
-        ASSERT_EQ(AINPUT_EVENT_TYPE_KEY, ev->getType());
+        ASSERT_EQ(InputEventType::KEY, ev->getType());
         KeyEvent *keyEvent = static_cast<KeyEvent *>(ev);
         EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, keyEvent->getAction());
         EXPECT_EQ(keycode, keyEvent->getKeyCode());
@@ -235,7 +235,7 @@
 
         ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
-        ASSERT_EQ(AINPUT_EVENT_TYPE_KEY, ev->getType());
+        ASSERT_EQ(InputEventType::KEY, ev->getType());
         keyEvent = static_cast<KeyEvent *>(ev);
         EXPECT_EQ(AMOTION_EVENT_ACTION_UP, keyEvent->getAction());
         EXPECT_EQ(keycode, keyEvent->getKeyCode());
@@ -272,8 +272,6 @@
         FocusRequest request;
         request.token = mInputInfo.token;
         request.windowName = mInputInfo.name;
-        request.focusedToken = nullptr;
-        request.focusedWindowName = "";
         request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
         request.displayId = displayId;
         t.setFocusedWindow(request);
@@ -512,6 +510,22 @@
     bgSurface->expectTap(1, 1);
 }
 
+TEST_F(InputSurfacesTest, input_respects_surface_insets_with_replaceTouchableRegionWithCrop) {
+    std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100);
+    std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100);
+    bgSurface->showAt(100, 100);
+
+    fgSurface->mInputInfo.surfaceInset = 5;
+    fgSurface->mInputInfo.replaceTouchableRegionWithCrop = true;
+    fgSurface->showAt(100, 100);
+
+    injectTap(106, 106);
+    fgSurface->expectTap(1, 1);
+
+    injectTap(101, 101);
+    bgSurface->expectTap(1, 1);
+}
+
 // Ensure a surface whose insets are cropped, handles the touch offset correctly. ref:b/120413463
 TEST_F(InputSurfacesTest, input_respects_cropped_surface_insets) {
     std::unique_ptr<InputSurface> parentSurface = makeSurface(100, 100);
@@ -614,7 +628,7 @@
 
     // Expect no crash for overflow.
     injectTap(12, 24);
-    fgSurface->expectTap(6, 12);
+    bgSurface->expectTap(12, 24);
 }
 
 // Ensure we ignore transparent region when getting screen bounds when positioning input frame.
@@ -1221,32 +1235,6 @@
     surface->expectKey(AKEYCODE_V);
 }
 
-/**
- * When multiple DisplayDevices are mapped to the same layerStack, use the configuration for the
- * display that can receive input.
- */
-TEST_F(MultiDisplayTests, many_to_one_display_mapping) {
-    ui::LayerStack layerStack = ui::LayerStack::fromValue(42);
-    createDisplay(1000, 1000, false /*isSecure*/, layerStack, false /*receivesInput*/,
-                  100 /*offsetX*/, 100 /*offsetY*/);
-    createDisplay(1000, 1000, false /*isSecure*/, layerStack, true /*receivesInput*/,
-                  200 /*offsetX*/, 200 /*offsetY*/);
-    createDisplay(1000, 1000, false /*isSecure*/, layerStack, false /*receivesInput*/,
-                  300 /*offsetX*/, 300 /*offsetY*/);
-    std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
-    surface->doTransaction([&](auto &t, auto &sc) { t.setLayerStack(sc, layerStack); });
-    surface->showAt(10, 10);
-
-    // Input injection happens in logical display coordinates.
-    injectTapOnDisplay(11, 11, layerStack.id);
-    // Expect that the display transform for the display that receives input was used.
-    surface->expectTapInDisplayCoordinates(211, 211);
-
-    surface->requestFocus(layerStack.id);
-    surface->assertFocusChange(true);
-    injectKeyOnDisplay(AKEYCODE_V, layerStack.id);
-}
-
 TEST_F(MultiDisplayTests, drop_input_for_secure_layer_on_nonsecure_display) {
     ui::LayerStack layerStack = ui::LayerStack::fromValue(42);
     createDisplay(1000, 1000, false /*isSecure*/, layerStack);
diff --git a/libs/gui/tests/GLTest.cpp b/libs/gui/tests/GLTest.cpp
index a1405fc..3ae4b6d 100644
--- a/libs/gui/tests/GLTest.cpp
+++ b/libs/gui/tests/GLTest.cpp
@@ -31,7 +31,7 @@
 void GLTest::SetUp() {
     const ::testing::TestInfo* const testInfo =
         ::testing::UnitTest::GetInstance()->current_test_info();
-    ALOGV("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name());
+    ALOGD("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name());
 
     mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
     ASSERT_EQ(EGL_SUCCESS, eglGetError());
@@ -135,7 +135,7 @@
 
     const ::testing::TestInfo* const testInfo =
         ::testing::UnitTest::GetInstance()->current_test_info();
-    ALOGV("End test:   %s.%s", testInfo->test_case_name(), testInfo->name());
+    ALOGD("End test:   %s.%s", testInfo->test_case_name(), testInfo->name());
 }
 
 EGLint const* GLTest::getConfigAttribs() {
diff --git a/libs/gui/tests/IGraphicBufferProducer_test.cpp b/libs/gui/tests/IGraphicBufferProducer_test.cpp
index 3427731..e6cb89c 100644
--- a/libs/gui/tests/IGraphicBufferProducer_test.cpp
+++ b/libs/gui/tests/IGraphicBufferProducer_test.cpp
@@ -84,7 +84,7 @@
     virtual void SetUp() {
         const ::testing::TestInfo* const testInfo =
             ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGV("Begin test: %s.%s", testInfo->test_case_name(),
+        ALOGD("Begin test: %s.%s", testInfo->test_case_name(),
                 testInfo->name());
 
         mMC = new MockConsumer;
@@ -114,7 +114,7 @@
     virtual void TearDown() {
         const ::testing::TestInfo* const testInfo =
             ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGV("End test:   %s.%s", testInfo->test_case_name(),
+        ALOGD("End test:   %s.%s", testInfo->test_case_name(),
                 testInfo->name());
     }
 
diff --git a/libs/gui/tests/StreamSplitter_test.cpp b/libs/gui/tests/StreamSplitter_test.cpp
index b65cdda..2f14924 100644
--- a/libs/gui/tests/StreamSplitter_test.cpp
+++ b/libs/gui/tests/StreamSplitter_test.cpp
@@ -36,14 +36,14 @@
     StreamSplitterTest() {
         const ::testing::TestInfo* const testInfo =
             ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGV("Begin test: %s.%s", testInfo->test_case_name(),
+        ALOGD("Begin test: %s.%s", testInfo->test_case_name(),
                 testInfo->name());
     }
 
     ~StreamSplitterTest() {
         const ::testing::TestInfo* const testInfo =
             ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGV("End test:   %s.%s", testInfo->test_case_name(),
+        ALOGD("End test:   %s.%s", testInfo->test_case_name(),
                 testInfo->name());
     }
 };
diff --git a/libs/gui/tests/SurfaceTextureClient_test.cpp b/libs/gui/tests/SurfaceTextureClient_test.cpp
index c7458a3..82b6697 100644
--- a/libs/gui/tests/SurfaceTextureClient_test.cpp
+++ b/libs/gui/tests/SurfaceTextureClient_test.cpp
@@ -42,7 +42,7 @@
     virtual void SetUp() {
         const ::testing::TestInfo* const testInfo =
             ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGV("Begin test: %s.%s", testInfo->test_case_name(),
+        ALOGD("Begin test: %s.%s", testInfo->test_case_name(),
                 testInfo->name());
 
         sp<IGraphicBufferProducer> producer;
@@ -99,7 +99,7 @@
 
         const ::testing::TestInfo* const testInfo =
             ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGV("End test:   %s.%s", testInfo->test_case_name(),
+        ALOGD("End test:   %s.%s", testInfo->test_case_name(),
                 testInfo->name());
     }
 
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index fccc408..096a43c 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -695,16 +695,14 @@
         mSupportsPresent = supportsPresent;
     }
 
-    status_t setTransactionState(const FrameTimelineInfo& /*frameTimelineInfo*/,
-                                 Vector<ComposerState>& /*state*/,
-                                 const Vector<DisplayState>& /*displays*/, uint32_t /*flags*/,
-                                 const sp<IBinder>& /*applyToken*/,
-                                 const InputWindowCommands& /*inputWindowCommands*/,
-                                 int64_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
-                                 const std::vector<client_cache_t>& /*cachedBuffer*/,
-                                 bool /*hasListenerCallbacks*/,
-                                 const std::vector<ListenerCallbacks>& /*listenerCallbacks*/,
-                                 uint64_t /*transactionId*/) override {
+    status_t setTransactionState(
+            const FrameTimelineInfo& /*frameTimelineInfo*/, Vector<ComposerState>& /*state*/,
+            const Vector<DisplayState>& /*displays*/, uint32_t /*flags*/,
+            const sp<IBinder>& /*applyToken*/, InputWindowCommands /*inputWindowCommands*/,
+            int64_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
+            const std::vector<client_cache_t>& /*cachedBuffer*/, bool /*hasListenerCallbacks*/,
+            const std::vector<ListenerCallbacks>& /*listenerCallbacks*/, uint64_t /*transactionId*/,
+            const std::vector<uint64_t>& /*mergedTransactionIds*/) override {
         return NO_ERROR;
     }
 
diff --git a/libs/gui/tests/VsyncEventData_test.cpp b/libs/gui/tests/VsyncEventData_test.cpp
index f114522..a2138f2 100644
--- a/libs/gui/tests/VsyncEventData_test.cpp
+++ b/libs/gui/tests/VsyncEventData_test.cpp
@@ -36,6 +36,7 @@
     FrameTimeline timeline1 = FrameTimeline{4, 5, 6};
     data.vsync.frameTimelines[0] = timeline0;
     data.vsync.frameTimelines[1] = timeline1;
+    data.vsync.frameTimelinesLength = 2;
 
     Parcel p;
     data.writeToParcel(&p);
@@ -45,7 +46,8 @@
     data2.readFromParcel(&p);
     ASSERT_EQ(data.vsync.frameInterval, data2.vsync.frameInterval);
     ASSERT_EQ(data.vsync.preferredFrameTimelineIndex, data2.vsync.preferredFrameTimelineIndex);
-    for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
+    ASSERT_EQ(data.vsync.frameTimelinesLength, data2.vsync.frameTimelinesLength);
+    for (int i = 0; i < VsyncEventData::kFrameTimelinesCapacity; i++) {
         ASSERT_EQ(data.vsync.frameTimelines[i].vsyncId, data2.vsync.frameTimelines[i].vsyncId);
         ASSERT_EQ(data.vsync.frameTimelines[i].deadlineTimestamp,
                   data2.vsync.frameTimelines[i].deadlineTimestamp);
diff --git a/libs/gui/tests/WindowInfo_test.cpp b/libs/gui/tests/WindowInfo_test.cpp
index c51b244..11b87ef 100644
--- a/libs/gui/tests/WindowInfo_test.cpp
+++ b/libs/gui/tests/WindowInfo_test.cpp
@@ -71,6 +71,7 @@
     i.applicationInfo.name = "ApplicationFooBar";
     i.applicationInfo.token = new BBinder();
     i.applicationInfo.dispatchingTimeoutMillis = 0x12345678ABCD;
+    i.focusTransferTarget = new BBinder();
 
     Parcel p;
     i.writeToParcel(&p);
@@ -101,6 +102,7 @@
     ASSERT_EQ(i.replaceTouchableRegionWithCrop, i2.replaceTouchableRegionWithCrop);
     ASSERT_EQ(i.touchableRegionCropHandle, i2.touchableRegionCropHandle);
     ASSERT_EQ(i.applicationInfo, i2.applicationInfo);
+    ASSERT_EQ(i.focusTransferTarget, i2.focusTransferTarget);
 }
 
 TEST(InputApplicationInfo, Parcelling) {
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..00925ba 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -79,25 +79,6 @@
     }
 }
 
-const char* motionToolTypeToString(int32_t toolType) {
-    switch (toolType) {
-        case AMOTION_EVENT_TOOL_TYPE_UNKNOWN:
-            return "UNKNOWN";
-        case AMOTION_EVENT_TOOL_TYPE_FINGER:
-            return "FINGER";
-        case AMOTION_EVENT_TOOL_TYPE_STYLUS:
-            return "STYLUS";
-        case AMOTION_EVENT_TOOL_TYPE_MOUSE:
-            return "MOUSE";
-        case AMOTION_EVENT_TOOL_TYPE_ERASER:
-            return "ERASER";
-        case AMOTION_EVENT_TOOL_TYPE_PALM:
-            return "PALM";
-        default:
-            return "INVALID";
-    }
-}
-
 // --- IdGenerator ---
 #if defined(__ANDROID__)
 [[maybe_unused]]
@@ -151,10 +132,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) {
@@ -176,30 +170,6 @@
     return atan2f(transformedPoint.x, -transformedPoint.y);
 }
 
-const char* inputEventTypeToString(int32_t type) {
-    switch (type) {
-        case AINPUT_EVENT_TYPE_KEY: {
-            return "KEY";
-        }
-        case AINPUT_EVENT_TYPE_MOTION: {
-            return "MOTION";
-        }
-        case AINPUT_EVENT_TYPE_FOCUS: {
-            return "FOCUS";
-        }
-        case AINPUT_EVENT_TYPE_CAPTURE: {
-            return "CAPTURE";
-        }
-        case AINPUT_EVENT_TYPE_DRAG: {
-            return "DRAG";
-        }
-        case AINPUT_EVENT_TYPE_TOUCH_MODE: {
-            return "TOUCH_MODE";
-        }
-    }
-    return "UNKNOWN";
-}
-
 std::string inputEventSourceToString(int32_t source) {
     if (source == AINPUT_SOURCE_UNKNOWN) {
         return "UNKNOWN";
@@ -243,8 +213,8 @@
     return (source & test) == test;
 }
 
-bool isStylusToolType(uint32_t toolType) {
-    return toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS || toolType == AMOTION_EVENT_TOOL_TYPE_ERASER;
+bool isStylusToolType(ToolType toolType) {
+    return toolType == ToolType::STYLUS || toolType == ToolType::ERASER;
 }
 
 VerifiedKeyEvent verifiedKeyEventFromKeyEvent(const KeyEvent& event) {
@@ -293,6 +263,37 @@
     return idGen.nextId();
 }
 
+std::ostream& operator<<(std::ostream& out, const InputEvent& event) {
+    switch (event.getType()) {
+        case InputEventType::KEY: {
+            const KeyEvent& keyEvent = static_cast<const KeyEvent&>(event);
+            out << keyEvent;
+            return out;
+        }
+        case InputEventType::MOTION: {
+            const MotionEvent& motionEvent = static_cast<const MotionEvent&>(event);
+            out << motionEvent;
+            return out;
+        }
+        case InputEventType::FOCUS: {
+            out << "FocusEvent";
+            return out;
+        }
+        case InputEventType::CAPTURE: {
+            out << "CaptureEvent";
+            return out;
+        }
+        case InputEventType::DRAG: {
+            out << "DragEvent";
+            return out;
+        }
+        case InputEventType::TOUCH_MODE: {
+            out << "TouchModeEvent";
+            return out;
+        }
+    }
+}
+
 // --- KeyEvent ---
 
 const char* KeyEvent::getLabel(int32_t keyCode) {
@@ -606,12 +607,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) {
@@ -797,7 +798,7 @@
         mPointerProperties.push_back({});
         PointerProperties& properties = mPointerProperties.back();
         properties.id = parcel->readInt32();
-        properties.toolType = parcel->readInt32();
+        properties.toolType = static_cast<ToolType>(parcel->readInt32());
     }
 
     while (sampleCount > 0) {
@@ -853,7 +854,7 @@
     for (size_t i = 0; i < pointerCount; i++) {
         const PointerProperties& properties = mPointerProperties[i];
         parcel->writeInt32(properties.id);
-        parcel->writeInt32(properties.toolType);
+        parcel->writeInt32(static_cast<int32_t>(properties.toolType));
     }
 
     const PointerCoords* pc = mSamplePointerCoords.data();
@@ -933,7 +934,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,
@@ -1017,9 +1018,9 @@
             out << ", x[" << i << "]=" << x;
             out << ", y[" << i << "]=" << y;
         }
-        int toolType = event.getToolType(i);
-        if (toolType != AMOTION_EVENT_TOOL_TYPE_FINGER) {
-            out << ", toolType[" << i << "]=" << toolType;
+        ToolType toolType = event.getToolType(i);
+        if (toolType != ToolType::FINGER) {
+            out << ", toolType[" << i << "]=" << ftl::enum_string(toolType);
         }
     }
     if (event.getButtonState() != 0) {
@@ -1171,44 +1172,51 @@
 
 void PooledInputEventFactory::recycle(InputEvent* event) {
     switch (event->getType()) {
-    case AINPUT_EVENT_TYPE_KEY:
-        if (mKeyEventPool.size() < mMaxPoolSize) {
-            mKeyEventPool.push(std::unique_ptr<KeyEvent>(static_cast<KeyEvent*>(event)));
-            return;
+        case InputEventType::KEY: {
+            if (mKeyEventPool.size() < mMaxPoolSize) {
+                mKeyEventPool.push(std::unique_ptr<KeyEvent>(static_cast<KeyEvent*>(event)));
+                return;
+            }
+            break;
         }
-        break;
-    case AINPUT_EVENT_TYPE_MOTION:
-        if (mMotionEventPool.size() < mMaxPoolSize) {
-            mMotionEventPool.push(std::unique_ptr<MotionEvent>(static_cast<MotionEvent*>(event)));
-            return;
+        case InputEventType::MOTION: {
+            if (mMotionEventPool.size() < mMaxPoolSize) {
+                mMotionEventPool.push(
+                        std::unique_ptr<MotionEvent>(static_cast<MotionEvent*>(event)));
+                return;
+            }
+            break;
         }
-        break;
-    case AINPUT_EVENT_TYPE_FOCUS:
-        if (mFocusEventPool.size() < mMaxPoolSize) {
-            mFocusEventPool.push(std::unique_ptr<FocusEvent>(static_cast<FocusEvent*>(event)));
-            return;
+        case InputEventType::FOCUS: {
+            if (mFocusEventPool.size() < mMaxPoolSize) {
+                mFocusEventPool.push(std::unique_ptr<FocusEvent>(static_cast<FocusEvent*>(event)));
+                return;
+            }
+            break;
         }
-        break;
-    case AINPUT_EVENT_TYPE_CAPTURE:
-        if (mCaptureEventPool.size() < mMaxPoolSize) {
-            mCaptureEventPool.push(
-                    std::unique_ptr<CaptureEvent>(static_cast<CaptureEvent*>(event)));
-            return;
+        case InputEventType::CAPTURE: {
+            if (mCaptureEventPool.size() < mMaxPoolSize) {
+                mCaptureEventPool.push(
+                        std::unique_ptr<CaptureEvent>(static_cast<CaptureEvent*>(event)));
+                return;
+            }
+            break;
         }
-        break;
-    case AINPUT_EVENT_TYPE_DRAG:
-        if (mDragEventPool.size() < mMaxPoolSize) {
-            mDragEventPool.push(std::unique_ptr<DragEvent>(static_cast<DragEvent*>(event)));
-            return;
+        case InputEventType::DRAG: {
+            if (mDragEventPool.size() < mMaxPoolSize) {
+                mDragEventPool.push(std::unique_ptr<DragEvent>(static_cast<DragEvent*>(event)));
+                return;
+            }
+            break;
         }
-        break;
-    case AINPUT_EVENT_TYPE_TOUCH_MODE:
-        if (mTouchModeEventPool.size() < mMaxPoolSize) {
-            mTouchModeEventPool.push(
-                    std::unique_ptr<TouchModeEvent>(static_cast<TouchModeEvent*>(event)));
-            return;
+        case InputEventType::TOUCH_MODE: {
+            if (mTouchModeEventPool.size() < mMaxPoolSize) {
+                mTouchModeEventPool.push(
+                        std::unique_ptr<TouchModeEvent>(static_cast<TouchModeEvent*>(event)));
+                return;
+            }
+            break;
         }
-        break;
     }
     delete event;
 }
diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp
index 4a19227..f99a7d6 100644
--- a/libs/input/InputEventLabels.cpp
+++ b/libs/input/InputEventLabels.cpp
@@ -343,7 +343,11 @@
     DEFINE_KEYCODE(STYLUS_BUTTON_SECONDARY), \
     DEFINE_KEYCODE(STYLUS_BUTTON_TERTIARY), \
     DEFINE_KEYCODE(STYLUS_BUTTON_TAIL), \
-    DEFINE_KEYCODE(RECENT_APPS)
+    DEFINE_KEYCODE(RECENT_APPS), \
+    DEFINE_KEYCODE(MACRO_1), \
+    DEFINE_KEYCODE(MACRO_2), \
+    DEFINE_KEYCODE(MACRO_3), \
+    DEFINE_KEYCODE(MACRO_4)
 
 // NOTE: If you add a new axis here you must also add it to several other files.
 //       Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list.
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 311b244..f6b4648 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -145,6 +145,10 @@
     return value ? "true" : "false";
 }
 
+static bool shouldResampleTool(ToolType toolType) {
+    return toolType == ToolType::FINGER || toolType == ToolType::UNKNOWN;
+}
+
 // --- InputMessage ---
 
 bool InputMessage::isValid(size_t actualSize) const {
@@ -1274,11 +1278,6 @@
     event->addSample(sampleTime, touchState.lastResample.pointers);
 }
 
-bool InputConsumer::shouldResampleTool(int32_t toolType) {
-    return toolType == AMOTION_EVENT_TOOL_TYPE_FINGER
-            || toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
-}
-
 status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) {
     ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
              "channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s",
diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp
index 737bd15..12c9e53 100644
--- a/libs/input/KeyCharacterMap.cpp
+++ b/libs/input/KeyCharacterMap.cpp
@@ -85,63 +85,7 @@
 
 // --- KeyCharacterMap ---
 
-KeyCharacterMap::KeyCharacterMap(const std::string& filename)
-      : mType(KeyboardType::UNKNOWN), mLoadFileName(filename) {}
-
-KeyCharacterMap::KeyCharacterMap(const KeyCharacterMap& other)
-      : mType(other.mType),
-        mLoadFileName(other.mLoadFileName),
-        mLayoutOverlayApplied(other.mLayoutOverlayApplied),
-        mKeyRemapping(other.mKeyRemapping),
-        mKeysByScanCode(other.mKeysByScanCode),
-        mKeysByUsageCode(other.mKeysByUsageCode) {
-    for (size_t i = 0; i < other.mKeys.size(); i++) {
-        mKeys.add(other.mKeys.keyAt(i), new Key(*other.mKeys.valueAt(i)));
-    }
-}
-
-KeyCharacterMap::~KeyCharacterMap() {
-    clear();
-}
-
-bool KeyCharacterMap::operator==(const KeyCharacterMap& other) const {
-    if (mType != other.mType) {
-        return false;
-    }
-    if (mLoadFileName != other.mLoadFileName) {
-        return false;
-    }
-    if (mLayoutOverlayApplied != other.mLayoutOverlayApplied) {
-        return false;
-    }
-    if (mKeys.size() != other.mKeys.size() || mKeyRemapping.size() != other.mKeyRemapping.size() ||
-        mKeysByScanCode.size() != other.mKeysByScanCode.size() ||
-        mKeysByUsageCode.size() != other.mKeysByUsageCode.size()) {
-        return false;
-    }
-
-    for (size_t i = 0; i < mKeys.size(); i++) {
-        if (mKeys.keyAt(i) != other.mKeys.keyAt(i)) {
-            return false;
-        }
-        const Key* key = mKeys.valueAt(i);
-        const Key* otherKey = other.mKeys.valueAt(i);
-        if (key->label != otherKey->label || key->number != otherKey->number) {
-            return false;
-        }
-    }
-
-    if (mKeyRemapping != other.mKeyRemapping || mKeysByScanCode != other.mKeysByScanCode ||
-        mKeysByUsageCode != other.mKeysByUsageCode) {
-        return false;
-    }
-
-    return true;
-}
-
-bool KeyCharacterMap::operator!=(const KeyCharacterMap& other) const {
-    return !(*this == other);
-}
+KeyCharacterMap::KeyCharacterMap(const std::string& filename) : mLoadFileName(filename) {}
 
 base::Result<std::shared_ptr<KeyCharacterMap>> KeyCharacterMap::load(const std::string& filename,
                                                                      Format format) {
@@ -207,10 +151,6 @@
 void KeyCharacterMap::clear() {
     mKeysByScanCode.clear();
     mKeysByUsageCode.clear();
-    for (size_t i = 0; i < mKeys.size(); i++) {
-        Key* key = mKeys.editValueAt(i);
-        delete key;
-    }
     mKeys.clear();
     mLayoutOverlayApplied = false;
     mType = KeyboardType::UNKNOWN;
@@ -233,24 +173,16 @@
     if (mLayoutOverlayApplied) {
         reloadBaseFromFile();
     }
-    for (size_t i = 0; i < overlay.mKeys.size(); i++) {
-        int32_t keyCode = overlay.mKeys.keyAt(i);
-        Key* key = overlay.mKeys.valueAt(i);
-        ssize_t oldIndex = mKeys.indexOfKey(keyCode);
-        if (oldIndex >= 0) {
-            delete mKeys.valueAt(oldIndex);
-            mKeys.editValueAt(oldIndex) = new Key(*key);
-        } else {
-            mKeys.add(keyCode, new Key(*key));
-        }
+    for (const auto& [keyCode, key] : overlay.mKeys) {
+        mKeys.insert_or_assign(keyCode, key);
     }
 
-    for (auto const& it : overlay.mKeysByScanCode) {
-        mKeysByScanCode.insert_or_assign(it.first, it.second);
+    for (const auto& [fromScanCode, toAndroidKeyCode] : overlay.mKeysByScanCode) {
+        mKeysByScanCode.insert_or_assign(fromScanCode, toAndroidKeyCode);
     }
 
-    for (auto const& it : overlay.mKeysByUsageCode) {
-        mKeysByUsageCode.insert_or_assign(it.first, it.second);
+    for (const auto& [fromHidUsageCode, toAndroidKeyCode] : overlay.mKeysByUsageCode) {
+        mKeysByUsageCode.insert_or_assign(fromHidUsageCode, toAndroidKeyCode);
     }
     mLayoutOverlayApplied = true;
 }
@@ -272,8 +204,8 @@
 
 char16_t KeyCharacterMap::getDisplayLabel(int32_t keyCode) const {
     char16_t result = 0;
-    const Key* key;
-    if (getKey(keyCode, &key)) {
+    const Key* key = getKey(keyCode);
+    if (key != nullptr) {
         result = key->label;
     }
 #if DEBUG_MAPPING
@@ -284,8 +216,8 @@
 
 char16_t KeyCharacterMap::getNumber(int32_t keyCode) const {
     char16_t result = 0;
-    const Key* key;
-    if (getKey(keyCode, &key)) {
+    const Key* key = getKey(keyCode);
+    if (key != nullptr) {
         result = key->number;
     }
 #if DEBUG_MAPPING
@@ -332,8 +264,8 @@
 char16_t KeyCharacterMap::getMatch(int32_t keyCode, const char16_t* chars, size_t numChars,
         int32_t metaState) const {
     char16_t result = 0;
-    const Key* key;
-    if (getKey(keyCode, &key)) {
+    const Key* key = getKey(keyCode);
+    if (key != nullptr) {
         // Try to find the most general behavior that maps to this character.
         // For example, the base key behavior will usually be last in the list.
         // However, if we find a perfect meta state match for one behavior then use that one.
@@ -343,19 +275,15 @@
                     if (behavior.character == chars[i]) {
                         result = behavior.character;
                         if ((behavior.metaState & metaState) == behavior.metaState) {
-                            goto ExactMatch;
+                            // Found exact match!
+                            return result;
                         }
                         break;
                     }
                 }
             }
         }
-    ExactMatch: ;
     }
-#if DEBUG_MAPPING
-    ALOGD("getMatch: keyCode=%d, chars=[%s], metaState=0x%08x ~ Result %d.",
-            keyCode, toString(chars, numChars).string(), metaState, result);
-#endif
     return result;
 }
 
@@ -493,19 +421,18 @@
     return std::make_pair(toKeyCode, toMetaState);
 }
 
-bool KeyCharacterMap::getKey(int32_t keyCode, const Key** outKey) const {
-    ssize_t index = mKeys.indexOfKey(keyCode);
-    if (index >= 0) {
-        *outKey = mKeys.valueAt(index);
-        return true;
+const KeyCharacterMap::Key* KeyCharacterMap::getKey(int32_t keyCode) const {
+    auto it = mKeys.find(keyCode);
+    if (it != mKeys.end()) {
+        return &it->second;
     }
-    return false;
+    return nullptr;
 }
 
 const KeyCharacterMap::Behavior* KeyCharacterMap::getKeyBehavior(int32_t keyCode,
                                                                  int32_t metaState) const {
-    const Key* key;
-    if (getKey(keyCode, &key)) {
+    const Key* key = getKey(keyCode);
+    if (key != nullptr) {
         for (const Behavior& behavior : key->behaviors) {
             if (matchesMetaState(metaState, behavior.metaState)) {
                 return &behavior;
@@ -551,19 +478,17 @@
         return false;
     }
 
-    for (size_t i = 0; i < mKeys.size(); i++) {
-        const Key* key = mKeys.valueAt(i);
-
+    for (const auto& [keyCode, key] : mKeys) {
         // Try to find the most general behavior that maps to this character.
         // For example, the base key behavior will usually be last in the list.
         const Behavior* found = nullptr;
-        for (const Behavior& behavior : key->behaviors) {
+        for (const Behavior& behavior : key.behaviors) {
             if (behavior.character == ch) {
                 found = &behavior;
             }
         }
         if (found != nullptr) {
-            *outKeyCode = mKeys.keyAt(i);
+            *outKeyCode = keyCode;
             *outMetaState = found->metaState;
             return true;
         }
@@ -715,11 +640,7 @@
             return nullptr;
         }
 
-        Key* key = new Key();
-        key->label = label;
-        key->number = number;
-        map->mKeys.add(keyCode, key);
-
+        Key key{.label = label, .number = number};
         while (parcel->readInt32()) {
             int32_t metaState = parcel->readInt32();
             char16_t character = parcel->readInt32();
@@ -729,13 +650,14 @@
                 return nullptr;
             }
 
-            key->behaviors.push_back({
+            key.behaviors.push_back({
                     .metaState = metaState,
                     .character = character,
                     .fallbackKeyCode = fallbackKeyCode,
                     .replacementKeyCode = replacementKeyCode,
             });
         }
+        map->mKeys.emplace(keyCode, std::move(key));
 
         if (parcel->errorCheck()) {
             return nullptr;
@@ -791,13 +713,11 @@
 
     size_t numKeys = mKeys.size();
     parcel->writeInt32(numKeys);
-    for (size_t i = 0; i < numKeys; i++) {
-        int32_t keyCode = mKeys.keyAt(i);
-        const Key* key = mKeys.valueAt(i);
+    for (const auto& [keyCode, key] : mKeys) {
         parcel->writeInt32(keyCode);
-        parcel->writeInt32(key->label);
-        parcel->writeInt32(key->number);
-        for (const Behavior& behavior : key->behaviors) {
+        parcel->writeInt32(key.label);
+        parcel->writeInt32(key.number);
+        for (const Behavior& behavior : key.behaviors) {
             parcel->writeInt32(1);
             parcel->writeInt32(behavior.metaState);
             parcel->writeInt32(behavior.character);
@@ -827,22 +747,12 @@
 }
 #endif // __linux__
 
-// --- KeyCharacterMap::Key ---
-
-KeyCharacterMap::Key::Key() : label(0), number(0) {}
-
-KeyCharacterMap::Key::Key(const Key& other)
-      : label(other.label), number(other.number), behaviors(other.behaviors) {}
-
 // --- KeyCharacterMap::Parser ---
 
 KeyCharacterMap::Parser::Parser(KeyCharacterMap* map, Tokenizer* tokenizer, Format format) :
         mMap(map), mTokenizer(tokenizer), mFormat(format), mState(STATE_TOP) {
 }
 
-KeyCharacterMap::Parser::~Parser() {
-}
-
 status_t KeyCharacterMap::Parser::parse() {
     while (!mTokenizer->isEof()) {
 #if DEBUG_PARSER
@@ -1022,7 +932,7 @@
                 keyCodeToken.string());
         return BAD_VALUE;
     }
-    if (mMap->mKeys.indexOfKey(*keyCode) >= 0) {
+    if (mMap->mKeys.find(*keyCode) != mMap->mKeys.end()) {
         ALOGE("%s: Duplicate entry for key code '%s'.", mTokenizer->getLocation().string(),
                 keyCodeToken.string());
         return BAD_VALUE;
@@ -1038,27 +948,27 @@
 
     ALOGD_IF(DEBUG_PARSER, "Parsed beginning of key: keyCode=%d.", *keyCode);
     mKeyCode = *keyCode;
-    mMap->mKeys.add(*keyCode, new Key());
+    mMap->mKeys.emplace(*keyCode, Key{});
     mState = STATE_KEY;
     return NO_ERROR;
 }
 
 status_t KeyCharacterMap::Parser::parseKeyProperty() {
-    Key* key = mMap->mKeys.valueFor(mKeyCode);
+    Key& key = mMap->mKeys[mKeyCode];
     String8 token = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER);
     if (token == "}") {
         mState = STATE_TOP;
         return finishKey(key);
     }
 
-    Vector<Property> properties;
+    std::vector<Property> properties;
 
     // Parse all comma-delimited property names up to the first colon.
     for (;;) {
         if (token == "label") {
-            properties.add(Property(PROPERTY_LABEL));
+            properties.emplace_back(PROPERTY_LABEL);
         } else if (token == "number") {
-            properties.add(Property(PROPERTY_NUMBER));
+            properties.emplace_back(PROPERTY_NUMBER);
         } else {
             int32_t metaState;
             status_t status = parseModifier(token.string(), &metaState);
@@ -1067,7 +977,7 @@
                         mTokenizer->getLocation().string(), token.string());
                 return status;
             }
-            properties.add(Property(PROPERTY_META, metaState));
+            properties.emplace_back(PROPERTY_META, metaState);
         }
 
         mTokenizer->skipDelimiters(WHITESPACE);
@@ -1182,47 +1092,44 @@
     } while (!mTokenizer->isEol() && mTokenizer->peekChar() != '#');
 
     // Add the behavior.
-    for (size_t i = 0; i < properties.size(); i++) {
-        const Property& property = properties.itemAt(i);
+    for (const Property& property : properties) {
         switch (property.property) {
         case PROPERTY_LABEL:
-            if (key->label) {
-                ALOGE("%s: Duplicate label for key.",
-                        mTokenizer->getLocation().string());
-                return BAD_VALUE;
-            }
-            key->label = behavior.character;
+                if (key.label) {
+                    ALOGE("%s: Duplicate label for key.", mTokenizer->getLocation().string());
+                    return BAD_VALUE;
+                }
+                key.label = behavior.character;
 #if DEBUG_PARSER
-            ALOGD("Parsed key label: keyCode=%d, label=%d.", mKeyCode, key->label);
+                ALOGD("Parsed key label: keyCode=%d, label=%d.", mKeyCode, key.label);
 #endif
             break;
         case PROPERTY_NUMBER:
-            if (key->number) {
-                ALOGE("%s: Duplicate number for key.",
-                        mTokenizer->getLocation().string());
-                return BAD_VALUE;
+            if (key.number) {
+                    ALOGE("%s: Duplicate number for key.", mTokenizer->getLocation().string());
+                    return BAD_VALUE;
             }
-            key->number = behavior.character;
+            key.number = behavior.character;
 #if DEBUG_PARSER
-            ALOGD("Parsed key number: keyCode=%d, number=%d.", mKeyCode, key->number);
+            ALOGD("Parsed key number: keyCode=%d, number=%d.", mKeyCode, key.number);
 #endif
             break;
         case PROPERTY_META: {
-            for (const Behavior& b : key->behaviors) {
-                if (b.metaState == property.metaState) {
+            for (const Behavior& b : key.behaviors) {
+                    if (b.metaState == property.metaState) {
                     ALOGE("%s: Duplicate key behavior for modifier.",
                             mTokenizer->getLocation().string());
                     return BAD_VALUE;
-                }
+                    }
             }
             Behavior newBehavior = behavior;
             newBehavior.metaState = property.metaState;
-            key->behaviors.push_front(newBehavior);
+            key.behaviors.push_front(newBehavior);
             ALOGD_IF(DEBUG_PARSER,
                      "Parsed key meta: keyCode=%d, meta=0x%x, char=%d, fallback=%d replace=%d.",
-                     mKeyCode, key->behaviors.front().metaState, key->behaviors.front().character,
-                     key->behaviors.front().fallbackKeyCode,
-                     key->behaviors.front().replacementKeyCode);
+                     mKeyCode, key.behaviors.front().metaState, key.behaviors.front().character,
+                     key.behaviors.front().fallbackKeyCode,
+                     key.behaviors.front().replacementKeyCode);
             break;
         }
         }
@@ -1230,12 +1137,12 @@
     return NO_ERROR;
 }
 
-status_t KeyCharacterMap::Parser::finishKey(Key* key) {
+status_t KeyCharacterMap::Parser::finishKey(Key& key) {
     // Fill in default number property.
-    if (!key->number) {
+    if (!key.number) {
         char16_t digit = 0;
         char16_t symbol = 0;
-        for (const Behavior& b : key->behaviors) {
+        for (const Behavior& b : key.behaviors) {
             char16_t ch = b.character;
             if (ch) {
                 if (ch >= '0' && ch <= '9') {
@@ -1247,7 +1154,7 @@
                 }
             }
         }
-        key->number = digit ? digit : symbol;
+        key.number = digit ? digit : symbol;
     }
     return NO_ERROR;
 }
diff --git a/libs/input/KeyLayoutMap.cpp b/libs/input/KeyLayoutMap.cpp
index a2649f6..a194513 100644
--- a/libs/input/KeyLayoutMap.cpp
+++ b/libs/input/KeyLayoutMap.cpp
@@ -247,6 +247,16 @@
     return scanCodes;
 }
 
+std::vector<int32_t> KeyLayoutMap::findUsageCodesForKey(int32_t keyCode) const {
+    std::vector<int32_t> usageCodes;
+    for (const auto& [usageCode, key] : mKeysByUsageCode) {
+        if (keyCode == key.keyCode) {
+            usageCodes.push_back(usageCode);
+        }
+    }
+    return usageCodes;
+}
+
 std::optional<AxisInfo> KeyLayoutMap::mapAxis(int32_t scanCode) const {
     auto it = mAxes.find(scanCode);
     if (it == mAxes.end()) {
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 b4151c6..3037573 100644
--- a/libs/input/MotionPredictor.cpp
+++ b/libs/input/MotionPredictor.cpp
@@ -30,6 +30,7 @@
 #include <log/log.h>
 
 #include <attestation/HmacKeyManager.h>
+#include <ftl/enum.h>
 #include <input/TfLiteMotionPredictor.h>
 
 namespace android {
@@ -108,10 +109,10 @@
         return {};
     }
 
-    const int32_t toolType = event.getPointerProperties(0)->toolType;
-    if (toolType != AMOTION_EVENT_TOOL_TYPE_STYLUS) {
+    const ToolType toolType = event.getPointerProperties(0)->toolType;
+    if (toolType != ToolType::STYLUS) {
         ALOGD_IF(isDebug(), "Prediction not supported for non-stylus tool: %s",
-                 motionToolTypeToString(toolType));
+                 ftl::enum_string(toolType).c_str());
         return {};
     }
 
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 691e87c..85fa176 100644
--- a/libs/input/TfLiteMotionPredictor.cpp
+++ b/libs/input/TfLiteMotionPredictor.cpp
@@ -61,8 +61,21 @@
 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";
@@ -217,7 +230,7 @@
 
 std::unique_ptr<TfLiteMotionPredictorModel> TfLiteMotionPredictorModel::create() {
     const std::string modelPath = getModelPath();
-    const int fd = open(modelPath.c_str(), O_RDONLY);
+    android::base::unique_fd fd(open(modelPath.c_str(), O_RDONLY));
     if (fd == -1) {
         PLOG(FATAL) << "Could not read model from " << modelPath;
     }
@@ -232,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)));
@@ -336,6 +346,10 @@
     return getTensorBuffer<const float>(mInputR).size();
 }
 
+size_t TfLiteMotionPredictorModel::outputLength() const {
+    return getTensorBuffer<const float>(mOutputR).size();
+}
+
 std::span<float> TfLiteMotionPredictorModel::inputR() {
     return getTensorBuffer<float>(mInputR);
 }
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/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp
index 3c1f2b6..9a459b1 100644
--- a/libs/input/VirtualInputDevice.cpp
+++ b/libs/input/VirtualInputDevice.cpp
@@ -44,15 +44,23 @@
     ioctl(mFd, UI_DEV_DESTROY);
 }
 
-bool VirtualInputDevice::writeInputEvent(uint16_t type, uint16_t code, int32_t value) {
+bool VirtualInputDevice::writeInputEvent(uint16_t type, uint16_t code, int32_t value,
+                                         std::chrono::nanoseconds eventTime) {
+    std::chrono::seconds seconds = std::chrono::duration_cast<std::chrono::seconds>(eventTime);
+    std::chrono::microseconds microseconds =
+            std::chrono::duration_cast<std::chrono::microseconds>(eventTime - seconds);
     struct input_event ev = {.type = type, .code = code, .value = value};
+    ev.input_event_sec = static_cast<decltype(ev.input_event_sec)>(seconds.count());
+    ev.input_event_usec = static_cast<decltype(ev.input_event_usec)>(microseconds.count());
+
     return TEMP_FAILURE_RETRY(write(mFd, &ev, sizeof(struct input_event))) == sizeof(ev);
 }
 
 /** Utility method to write keyboard key events or mouse button events. */
 bool VirtualInputDevice::writeEvKeyEvent(int32_t androidCode, int32_t androidAction,
                                          const std::map<int, int>& evKeyCodeMapping,
-                                         const std::map<int, UinputAction>& actionMapping) {
+                                         const std::map<int, UinputAction>& actionMapping,
+                                         std::chrono::nanoseconds eventTime) {
     auto evKeyCodeIterator = evKeyCodeMapping.find(androidCode);
     if (evKeyCodeIterator == evKeyCodeMapping.end()) {
         ALOGE("Unsupported native EV keycode for android code %d", androidCode);
@@ -63,10 +71,10 @@
         return false;
     }
     if (!writeInputEvent(EV_KEY, static_cast<uint16_t>(evKeyCodeIterator->second),
-                         static_cast<int32_t>(actionIterator->second))) {
+                         static_cast<int32_t>(actionIterator->second), eventTime)) {
         return false;
     }
-    if (!writeInputEvent(EV_SYN, SYN_REPORT, 0)) {
+    if (!writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime)) {
         return false;
     }
     return true;
@@ -189,8 +197,10 @@
 VirtualKeyboard::VirtualKeyboard(unique_fd fd) : VirtualInputDevice(std::move(fd)) {}
 VirtualKeyboard::~VirtualKeyboard() {}
 
-bool VirtualKeyboard::writeKeyEvent(int32_t androidKeyCode, int32_t androidAction) {
-    return writeEvKeyEvent(androidKeyCode, androidAction, KEY_CODE_MAPPING, KEY_ACTION_MAPPING);
+bool VirtualKeyboard::writeKeyEvent(int32_t androidKeyCode, int32_t androidAction,
+                                    std::chrono::nanoseconds eventTime) {
+    return writeEvKeyEvent(androidKeyCode, androidAction, KEY_CODE_MAPPING, KEY_ACTION_MAPPING,
+                           eventTime);
 }
 
 // --- VirtualDpad ---
@@ -210,9 +220,10 @@
 
 VirtualDpad::~VirtualDpad() {}
 
-bool VirtualDpad::writeDpadKeyEvent(int32_t androidKeyCode, int32_t androidAction) {
+bool VirtualDpad::writeDpadKeyEvent(int32_t androidKeyCode, int32_t androidAction,
+                                    std::chrono::nanoseconds eventTime) {
     return writeEvKeyEvent(androidKeyCode, androidAction, DPAD_KEY_CODE_MAPPING,
-                           VirtualKeyboard::KEY_ACTION_MAPPING);
+                           VirtualKeyboard::KEY_ACTION_MAPPING, eventTime);
 }
 
 // --- VirtualMouse ---
@@ -236,20 +247,24 @@
 
 VirtualMouse::~VirtualMouse() {}
 
-bool VirtualMouse::writeButtonEvent(int32_t androidButtonCode, int32_t androidAction) {
+bool VirtualMouse::writeButtonEvent(int32_t androidButtonCode, int32_t androidAction,
+                                    std::chrono::nanoseconds eventTime) {
     return writeEvKeyEvent(androidButtonCode, androidAction, BUTTON_CODE_MAPPING,
-                           BUTTON_ACTION_MAPPING);
+                           BUTTON_ACTION_MAPPING, eventTime);
 }
 
-bool VirtualMouse::writeRelativeEvent(float relativeX, float relativeY) {
-    return writeInputEvent(EV_REL, REL_X, relativeX) && writeInputEvent(EV_REL, REL_Y, relativeY) &&
-            writeInputEvent(EV_SYN, SYN_REPORT, 0);
+bool VirtualMouse::writeRelativeEvent(float relativeX, float relativeY,
+                                      std::chrono::nanoseconds eventTime) {
+    return writeInputEvent(EV_REL, REL_X, relativeX, eventTime) &&
+            writeInputEvent(EV_REL, REL_Y, relativeY, eventTime) &&
+            writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime);
 }
 
-bool VirtualMouse::writeScrollEvent(float xAxisMovement, float yAxisMovement) {
-    return writeInputEvent(EV_REL, REL_HWHEEL, xAxisMovement) &&
-            writeInputEvent(EV_REL, REL_WHEEL, yAxisMovement) &&
-            writeInputEvent(EV_SYN, SYN_REPORT, 0);
+bool VirtualMouse::writeScrollEvent(float xAxisMovement, float yAxisMovement,
+                                    std::chrono::nanoseconds eventTime) {
+    return writeInputEvent(EV_REL, REL_HWHEEL, xAxisMovement, eventTime) &&
+            writeInputEvent(EV_REL, REL_WHEEL, yAxisMovement, eventTime) &&
+            writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime);
 }
 
 // --- VirtualTouchscreen ---
@@ -291,7 +306,7 @@
 
 bool VirtualTouchscreen::writeTouchEvent(int32_t pointerId, int32_t toolType, int32_t action,
                                          float locationX, float locationY, float pressure,
-                                         float majorAxisSize) {
+                                         float majorAxisSize, std::chrono::nanoseconds eventTime) {
     auto actionIterator = TOUCH_ACTION_MAPPING.find(action);
     if (actionIterator == TOUCH_ACTION_MAPPING.end()) {
         return false;
@@ -300,44 +315,44 @@
     if (!isValidPointerId(pointerId, uinputAction)) {
         return false;
     }
-    if (!writeInputEvent(EV_ABS, ABS_MT_SLOT, pointerId)) {
+    if (!writeInputEvent(EV_ABS, ABS_MT_SLOT, pointerId, eventTime)) {
         return false;
     }
     auto toolTypeIterator = TOOL_TYPE_MAPPING.find(toolType);
     if (toolTypeIterator == TOOL_TYPE_MAPPING.end()) {
         return false;
     }
-    if (!writeInputEvent(EV_ABS, ABS_MT_TOOL_TYPE,
-                         static_cast<int32_t>(toolTypeIterator->second))) {
+    if (!writeInputEvent(EV_ABS, ABS_MT_TOOL_TYPE, static_cast<int32_t>(toolTypeIterator->second),
+                         eventTime)) {
         return false;
     }
-    if (uinputAction == UinputAction::PRESS && !handleTouchDown(pointerId)) {
+    if (uinputAction == UinputAction::PRESS && !handleTouchDown(pointerId, eventTime)) {
         return false;
     }
-    if (uinputAction == UinputAction::RELEASE && !handleTouchUp(pointerId)) {
+    if (uinputAction == UinputAction::RELEASE && !handleTouchUp(pointerId, eventTime)) {
         return false;
     }
-    if (!writeInputEvent(EV_ABS, ABS_MT_POSITION_X, locationX)) {
+    if (!writeInputEvent(EV_ABS, ABS_MT_POSITION_X, locationX, eventTime)) {
         return false;
     }
-    if (!writeInputEvent(EV_ABS, ABS_MT_POSITION_Y, locationY)) {
+    if (!writeInputEvent(EV_ABS, ABS_MT_POSITION_Y, locationY, eventTime)) {
         return false;
     }
     if (!isnan(pressure)) {
-        if (!writeInputEvent(EV_ABS, ABS_MT_PRESSURE, pressure)) {
+        if (!writeInputEvent(EV_ABS, ABS_MT_PRESSURE, pressure, eventTime)) {
             return false;
         }
     }
     if (!isnan(majorAxisSize)) {
-        if (!writeInputEvent(EV_ABS, ABS_MT_TOUCH_MAJOR, majorAxisSize)) {
+        if (!writeInputEvent(EV_ABS, ABS_MT_TOUCH_MAJOR, majorAxisSize, eventTime)) {
             return false;
         }
     }
-    return writeInputEvent(EV_SYN, SYN_REPORT, 0);
+    return writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime);
 }
 
-bool VirtualTouchscreen::handleTouchUp(int32_t pointerId) {
-    if (!writeInputEvent(EV_ABS, ABS_MT_TRACKING_ID, static_cast<int32_t>(-1))) {
+bool VirtualTouchscreen::handleTouchUp(int32_t pointerId, std::chrono::nanoseconds eventTime) {
+    if (!writeInputEvent(EV_ABS, ABS_MT_TRACKING_ID, static_cast<int32_t>(-1), eventTime)) {
         return false;
     }
     // When a pointer is no longer in touch, remove the pointer id from the corresponding
@@ -347,7 +362,8 @@
 
     // Only sends the BTN UP event when there's no pointers on the touchscreen.
     if (mActivePointers.none()) {
-        if (!writeInputEvent(EV_KEY, BTN_TOUCH, static_cast<int32_t>(UinputAction::RELEASE))) {
+        if (!writeInputEvent(EV_KEY, BTN_TOUCH, static_cast<int32_t>(UinputAction::RELEASE),
+                             eventTime)) {
             return false;
         }
         ALOGD_IF(isDebug(), "No pointers on touchscreen %d, BTN UP event sent.", mFd.get());
@@ -355,12 +371,13 @@
     return true;
 }
 
-bool VirtualTouchscreen::handleTouchDown(int32_t pointerId) {
+bool VirtualTouchscreen::handleTouchDown(int32_t pointerId, std::chrono::nanoseconds eventTime) {
     // When a new pointer is down on the touchscreen, add the pointer id in the corresponding
     // entry in the unreleased touches map.
     if (mActivePointers.none()) {
         // Only sends the BTN Down event when the first pointer on the touchscreen is down.
-        if (!writeInputEvent(EV_KEY, BTN_TOUCH, static_cast<int32_t>(UinputAction::PRESS))) {
+        if (!writeInputEvent(EV_KEY, BTN_TOUCH, static_cast<int32_t>(UinputAction::PRESS),
+                             eventTime)) {
             return false;
         }
         ALOGD_IF(isDebug(), "First pointer %d down under touchscreen %d, BTN DOWN event sent",
@@ -369,7 +386,7 @@
 
     mActivePointers.set(pointerId);
     ALOGD_IF(isDebug(), "Added pointer %d under touchscreen %d in the map", pointerId, mFd.get());
-    if (!writeInputEvent(EV_ABS, ABS_MT_TRACKING_ID, static_cast<int32_t>(pointerId))) {
+    if (!writeInputEvent(EV_ABS, ABS_MT_TRACKING_ID, static_cast<int32_t>(pointerId), eventTime)) {
         return false;
     }
     return true;
diff --git a/libs/input/android/os/InputEventInjectionSync.aidl b/libs/input/android/os/InputEventInjectionSync.aidl
index 95d24cb..2d225fa 100644
--- a/libs/input/android/os/InputEventInjectionSync.aidl
+++ b/libs/input/android/os/InputEventInjectionSync.aidl
@@ -33,4 +33,7 @@
 
     /* Waits for the input event to be completely processed. */
     WAIT_FOR_FINISHED = 2,
+
+    ftl_first = NONE,
+    ftl_last = WAIT_FOR_FINISHED,
 }
diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp
index 8a6e983..a965573 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,
@@ -195,7 +197,7 @@
                      ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME);
 
     ASSERT_EQ(id, event.getId());
-    ASSERT_EQ(AINPUT_EVENT_TYPE_KEY, event.getType());
+    ASSERT_EQ(InputEventType::KEY, event.getType());
     ASSERT_EQ(2, event.getDeviceId());
     ASSERT_EQ(AINPUT_SOURCE_GAMEPAD, event.getSource());
     ASSERT_EQ(DISPLAY_ID, event.getDisplayId());
@@ -235,108 +237,116 @@
     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 = ToolType::FINGER;
+    mPointerProperties[1].clear();
+    mPointerProperties[1].id = 2;
+    mPointerProperties[1].toolType = ToolType::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) {
     // Check properties.
     ASSERT_EQ(mId, event->getId());
-    ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType());
+    ASSERT_EQ(InputEventType::MOTION, event->getType());
     ASSERT_EQ(2, event->getDeviceId());
     ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, event->getSource());
     ASSERT_EQ(DISPLAY_ID, event->getDisplayId());
@@ -356,9 +366,9 @@
 
     ASSERT_EQ(2U, event->getPointerCount());
     ASSERT_EQ(1, event->getPointerId(0));
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, event->getToolType(0));
+    ASSERT_EQ(ToolType::FINGER, event->getToolType(0));
     ASSERT_EQ(2, event->getPointerId(1));
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, event->getToolType(1));
+    ASSERT_EQ(ToolType::STYLUS, event->getToolType(1));
 
     ASSERT_EQ(2U, event->getHistorySize());
 
@@ -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;
@@ -668,7 +692,7 @@
 MotionEvent createMotionEvent(int32_t source, uint32_t action, float x, float y, float dx, float dy,
                               const ui::Transform& transform, const ui::Transform& rawTransform) {
     std::vector<PointerProperties> pointerProperties;
-    pointerProperties.push_back(PointerProperties{/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER});
+    pointerProperties.push_back(PointerProperties{/*id=*/0, ToolType::FINGER});
     std::vector<PointerCoords> pointerCoords;
     pointerCoords.emplace_back().clear();
     pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_X, x);
@@ -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..3ecf8ee 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,14 +92,13 @@
 
     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";
-    ASSERT_EQ(AINPUT_EVENT_TYPE_KEY, event->getType())
-            << "consumer should have returned a key event";
+    ASSERT_EQ(InputEventType::KEY, event->getType()) << "consumer should have returned a key event";
 
     KeyEvent* keyEvent = static_cast<KeyEvent*>(event);
     EXPECT_EQ(seq, consumeSeq);
@@ -170,7 +171,7 @@
     for (size_t i = 0; i < pointerCount; i++) {
         pointerProperties[i].clear();
         pointerProperties[i].id = (i + 2) % pointerCount;
-        pointerProperties[i].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+        pointerProperties[i].toolType = ToolType::FINGER;
 
         pointerCoords[i].clear();
         pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, 100 * i);
@@ -199,13 +200,13 @@
 
     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";
-    ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType())
+    ASSERT_EQ(InputEventType::MOTION, event->getType())
             << "consumer should have returned a motion event";
 
     MotionEvent* motionEvent = static_cast<MotionEvent*>(event);
@@ -226,10 +227,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 +243,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,11 +293,11 @@
 
     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";
-    ASSERT_EQ(AINPUT_EVENT_TYPE_FOCUS, event->getType())
+    ASSERT_EQ(InputEventType::FOCUS, event->getType())
             << "consumer should have returned a focus event";
 
     FocusEvent* focusEvent = static_cast<FocusEvent*>(event);
@@ -331,11 +334,11 @@
 
     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";
-    ASSERT_EQ(AINPUT_EVENT_TYPE_CAPTURE, event->getType())
+    ASSERT_EQ(InputEventType::CAPTURE, event->getType())
             << "consumer should have returned a capture event";
 
     const CaptureEvent* captureEvent = static_cast<CaptureEvent*>(event);
@@ -373,11 +376,11 @@
 
     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";
-    ASSERT_EQ(AINPUT_EVENT_TYPE_DRAG, event->getType())
+    ASSERT_EQ(InputEventType::DRAG, event->getType())
             << "consumer should have returned a drag event";
 
     const DragEvent& dragEvent = static_cast<const DragEvent&>(*event);
@@ -415,11 +418,11 @@
 
     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";
-    ASSERT_EQ(AINPUT_EVENT_TYPE_TOUCH_MODE, event->getType())
+    ASSERT_EQ(InputEventType::TOUCH_MODE, event->getType())
             << "consumer should have returned a touch mode event";
 
     const TouchModeEvent& touchModeEvent = static_cast<const TouchModeEvent&>(*event);
diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp
index c61efbf..7a62f5e 100644
--- a/libs/input/tests/MotionPredictor_test.cpp
+++ b/libs/input/tests/MotionPredictor_test.cpp
@@ -45,7 +45,7 @@
         PointerProperties properties;
         properties.clear();
         properties.id = i;
-        properties.toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+        properties.toolType = ToolType::STYLUS;
         pointerProperties.push_back(properties);
         PointerCoords coords;
         coords.clear();
diff --git a/libs/input/tests/RingBuffer_test.cpp b/libs/input/tests/RingBuffer_test.cpp
index 8a6ef4c..a2ef658 100644
--- a/libs/input/tests/RingBuffer_test.cpp
+++ b/libs/input/tests/RingBuffer_test.cpp
@@ -118,6 +118,21 @@
     EXPECT_EQ(0u, d.capacity());
 }
 
+TEST(RingBufferTest, FrontBackAccess) {
+    RingBuffer<int> buffer(/*capacity=*/2);
+    buffer.pushBack(1);
+    EXPECT_EQ(1, buffer.front());
+    EXPECT_EQ(1, buffer.back());
+
+    buffer.pushFront(0);
+    EXPECT_EQ(0, buffer.front());
+    EXPECT_EQ(1, buffer.back());
+
+    buffer.pushFront(-1);
+    EXPECT_EQ(-1, buffer.front());
+    EXPECT_EQ(0, buffer.back());
+}
+
 TEST(RingBufferTest, Subscripting) {
     RingBuffer<int> buffer(/*capacity=*/2);
     buffer.pushBack(1);
diff --git a/libs/input/tests/TfLiteMotionPredictor_test.cpp b/libs/input/tests/TfLiteMotionPredictor_test.cpp
index 6e76ac1..b5ed9e4 100644
--- a/libs/input/tests/TfLiteMotionPredictor_test.cpp
+++ b/libs/input/tests/TfLiteMotionPredictor_test.cpp
@@ -139,8 +139,10 @@
 
     ASSERT_TRUE(model->invoke());
 
-    ASSERT_EQ(model->outputR().size(), model->outputPhi().size());
-    ASSERT_EQ(model->outputR().size(), model->outputPressure().size());
+    const int outputLength = model->outputLength();
+    ASSERT_EQ(outputLength, model->outputR().size());
+    ASSERT_EQ(outputLength, model->outputPhi().size());
+    ASSERT_EQ(outputLength, model->outputPressure().size());
 }
 
 TEST(TfLiteMotionPredictorTest, ModelOutput) {
diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp
index d01258c..655de80 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(),
@@ -99,7 +99,7 @@
         properties.push_back({});
         properties.back().clear();
         properties.back().id = pointer.id;
-        properties.back().toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+        properties.back().toolType = ToolType::FINGER;
 
         coords.push_back({});
         coords.back().clear();
@@ -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..ae72109 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);
     }
@@ -212,19 +212,19 @@
             coords[pointerIndex].isResampled = position.isResampled;
 
             properties[pointerIndex].id = pointerId;
-            properties[pointerIndex].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+            properties[pointerIndex].toolType = ToolType::FINGER;
             pointerIndex++;
         }
         EXPECT_EQ(pointerIndex, pointerCount);
 
         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/include/jpegrecoverymap/jpegr.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h
deleted file mode 100644
index a433e8a..0000000
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h
+++ /dev/null
@@ -1,335 +0,0 @@
-/*
- * 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_JPEGR_H
-#define ANDROID_JPEGRECOVERYMAP_JPEGR_H
-
-#include "jpegrerrorcode.h"
-
-namespace android::jpegrecoverymap {
-
-// Color gamuts for image data
-typedef enum {
-  JPEGR_COLORGAMUT_UNSPECIFIED,
-  JPEGR_COLORGAMUT_BT709,
-  JPEGR_COLORGAMUT_P3,
-  JPEGR_COLORGAMUT_BT2100,
-} jpegr_color_gamut;
-
-// Transfer functions for image data
-typedef enum {
-  JPEGR_TF_UNSPECIFIED = -1,
-  JPEGR_TF_LINEAR = 0,
-  JPEGR_TF_HLG = 1,
-  JPEGR_TF_PQ = 2,
-  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;
-    std::vector<uint8_t>* iccData;
-    std::vector<uint8_t>* exifData;
-};
-
-/*
- * Holds information for uncompressed image or recovery map.
- */
-struct jpegr_uncompressed_struct {
-    // Pointer to the data location.
-    void* data;
-    // Width of the recovery map or image in pixels.
-    int width;
-    // Height of the recovery map or image in pixels.
-    int height;
-    // Color gamut.
-    jpegr_color_gamut colorGamut;
-};
-
-/*
- * Holds information for compressed image or recovery map.
- */
-struct jpegr_compressed_struct {
-    // Pointer to the data location.
-    void* data;
-    // Used data length in bytes.
-    int length;
-    // Maximum available data length in bytes.
-    int maxLength;
-    // Color gamut.
-    jpegr_color_gamut colorGamut;
-};
-
-/*
- * Holds information for EXIF metadata.
- */
-struct jpegr_exif_struct {
-    // Pointer to the data location.
-    void* data;
-    // Data length;
-    int length;
-};
-
-struct jpegr_metadata {
-  // JPEG/R version
-  uint32_t version;
-  // Max Content Boost for the map
-  float maxContentBoost;
-  // Min Content Boost for the map
-  float minContentBoost;
-};
-
-typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr;
-typedef struct jpegr_compressed_struct* jr_compressed_ptr;
-typedef struct jpegr_exif_struct* jr_exif_ptr;
-typedef struct jpegr_metadata* jr_metadata_ptr;
-typedef struct jpegr_info_struct* jr_info_ptr;
-
-class JpegR {
-public:
-    /*
-     * Encode API-0
-     * Compress JPEGR image from 10-bit HDR YUV.
-     *
-     * Tonemap the HDR input to a SDR image, generate recovery map from the HDR and SDR images,
-     * compress SDR YUV to 8-bit JPEG and append the recovery map to the end of the compressed
-     * JPEG.
-     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
-     * @param hdr_tf transfer function of the HDR image
-     * @param dest destination of the compressed JPEGR image
-     * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is
-     *                the highest quality
-     * @param exif pointer to the exif metadata.
-     * @return NO_ERROR if encoding succeeds, error code if error occurs.
-     */
-    status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
-                         jpegr_transfer_function hdr_tf,
-                         jr_compressed_ptr dest,
-                         int quality,
-                         jr_exif_ptr exif);
-
-    /*
-     * Encode API-1
-     * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV.
-     *
-     * Generate recovery map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append
-     * the recovery map to the end of the compressed JPEG. HDR and SDR inputs must be the same
-     * resolution.
-     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
-     * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
-     * @param hdr_tf transfer function of the HDR image
-     * @param dest destination of the compressed JPEGR image
-     * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is
-     *                the highest quality
-     * @param exif pointer to the exif metadata.
-     * @return NO_ERROR if encoding succeeds, error code if error occurs.
-     */
-    status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
-                         jr_uncompressed_ptr uncompressed_yuv_420_image,
-                         jpegr_transfer_function hdr_tf,
-                         jr_compressed_ptr dest,
-                         int quality,
-                         jr_exif_ptr exif);
-
-    /*
-     * Encode API-2
-     * Compress JPEGR image from 10-bit HDR YUV, 8-bit SDR YUV and compressed 8-bit JPEG.
-     *
-     * This method requires HAL Hardware JPEG encoder.
-     *
-     * Generate recovery map from the HDR and SDR inputs, append the recovery map to the end of the
-     * compressed JPEG. HDR and SDR inputs must be the same resolution and color space.
-     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
-     * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
-     *                                   Note: the SDR image must be the decoded version of the JPEG
-     *                                         input
-     * @param compressed_jpeg_image compressed 8-bit JPEG image
-     * @param hdr_tf transfer function of the HDR image
-     * @param dest destination of the compressed JPEGR image
-     * @return NO_ERROR if encoding succeeds, error code if error occurs.
-     */
-    status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
-                         jr_uncompressed_ptr uncompressed_yuv_420_image,
-                         jr_compressed_ptr compressed_jpeg_image,
-                         jpegr_transfer_function hdr_tf,
-                         jr_compressed_ptr dest);
-
-    /*
-     * Encode API-3
-     * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV.
-     *
-     * This method requires HAL Hardware JPEG encoder.
-     *
-     * Decode the compressed 8-bit JPEG image to YUV SDR, generate recovery map from the HDR input
-     * and the decoded SDR result, append the recovery map to the end of the compressed JPEG. HDR
-     * and SDR inputs must be the same resolution.
-     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
-     * @param compressed_jpeg_image compressed 8-bit JPEG image
-     * @param hdr_tf transfer function of the HDR image
-     * @param dest destination of the compressed JPEGR image
-     * @return NO_ERROR if encoding succeeds, error code if error occurs.
-     */
-    status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
-                         jr_compressed_ptr compressed_jpeg_image,
-                         jpegr_transfer_function hdr_tf,
-                         jr_compressed_ptr dest);
-
-    /*
-     * 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 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.
-     * @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,
-                         jpegr_output_format output_format = JPEGR_OUTPUT_HDR_LINEAR);
-
-    /*
-    * Gets Info from JPEGR file without decoding it.
-    *
-    * The output is filled jpegr_info structure
-    * @param compressed_jpegr_image compressed JPEGR image
-    * @param jpegr_info pointer to output JPEGR info. Members of jpegr_info
-    *         are owned by the caller
-    * @return NO_ERROR if JPEGR parsing succeeds, error code otherwise
-    */
-    status_t getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
-                          jr_info_ptr jpegr_info);
-protected:
-    /*
-     * This method is called in the encoding pipeline. It will take the uncompressed 8-bit and
-     * 10-bit yuv images as input, and calculate the uncompressed recovery map. The input images
-     * must be the same resolution.
-     *
-     * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
-     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
-     * @param hdr_tf transfer function of the HDR image
-     * @param dest recovery map; caller responsible for memory of data
-     * @param metadata max_content_boost is filled in
-     * @return NO_ERROR if calculation succeeds, error code if error occurs.
-     */
-    status_t generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
-                                 jr_uncompressed_ptr uncompressed_p010_image,
-                                 jpegr_transfer_function hdr_tf,
-                                 jr_metadata_ptr metadata,
-                                 jr_uncompressed_ptr dest);
-
-    /*
-     * This method is called in the decoding pipeline. It will take the uncompressed (decoded)
-     * 8-bit yuv image, the uncompressed (decoded) recovery map, and extracted JPEG/R metadata as
-     * input, and calculate the 10-bit recovered image. The recovered output image is the same
-     * color gamut as the SDR image, with HLG transfer function, and is in RGBA1010102 data format.
-     *
-     * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
-     * @param uncompressed_recovery_map uncompressed recovery map
-     * @param metadata JPEG/R metadata extracted from XMP.
-     * @param 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:
-    /*
-     * This method is called in the encoding pipeline. It will encode the recovery map.
-     *
-     * @param uncompressed_recovery_map uncompressed recovery map
-     * @param dest encoded recover map
-     * @return NO_ERROR if encoding succeeds, error code if error occurs.
-     */
-    status_t compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
-                               jr_compressed_ptr dest);
-
-    /*
-     * This methoud is called to separate primary image and recovery map image from JPEGR
-     *
-     * @param compressed_jpegr_image compressed JPEGR image
-     * @param primary_image destination of primary image
-     * @param recovery_map destination of compressed recovery map
-     * @return NO_ERROR if calculation succeeds, error code if error occurs.
-    */
-    status_t extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
-                                               jr_compressed_ptr primary_image,
-                                               jr_compressed_ptr recovery_map);
-    /*
-     * This method is called in the decoding pipeline. It will read XMP metadata to find the start
-     * position of the compressed recovery map, and will extract the compressed recovery map.
-     *
-     * @param compressed_jpegr_image compressed JPEGR image
-     * @param dest destination of compressed recovery map
-     * @return NO_ERROR if calculation succeeds, error code if error occurs.
-     */
-    status_t extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
-                                jr_compressed_ptr dest);
-
-    /*
-     * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image,
-     * the compressed recovery map and optionally the exif package as inputs, and generate the XMP
-     * metadata, and finally append everything in the order of:
-     *     SOI, APP2(EXIF) (if EXIF is from outside), APP2(XMP), primary image, recovery map
-     * Note that EXIF package is only available for encoding API-0 and API-1. For encoding API-2 and
-     * API-3 this parameter is null, but the primary image in JPEG/R may still have EXIF as long as
-     * the input JPEG has EXIF.
-     *
-     * @param compressed_jpeg_image compressed 8-bit JPEG image
-     * @param compress_recovery_map compressed recover map
-     * @param (nullable) exif EXIF package
-     * @param metadata JPEG/R metadata to encode in XMP of the jpeg
-     * @param dest compressed JPEGR image
-     * @return NO_ERROR if calculation succeeds, error code if error occurs.
-     */
-    status_t appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
-                               jr_compressed_ptr compressed_recovery_map,
-                               jr_exif_ptr exif,
-                               jr_metadata_ptr metadata,
-                               jr_compressed_ptr dest);
-
-    /*
-     * This method will tone map a HDR image to an SDR image.
-     *
-     * @param src (input) uncompressed P010 image
-     * @param dest (output) tone mapping result as a YUV_420 image
-     * @return NO_ERROR if calculation succeeds, error code if error occurs.
-     */
-    status_t toneMap(jr_uncompressed_ptr src,
-                     jr_uncompressed_ptr dest);
-};
-
-} // namespace android::jpegrecoverymap
-
-#endif // ANDROID_JPEGRECOVERYMAP_JPEGR_H
diff --git a/libs/jpegrecoverymap/tests/jpegencoderhelper_test.cpp b/libs/jpegrecoverymap/tests/jpegencoderhelper_test.cpp
deleted file mode 100644
index 095ac2f..0000000
--- a/libs/jpegrecoverymap/tests/jpegencoderhelper_test.cpp
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * 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/jpegencoderhelper.h>
-#include <gtest/gtest.h>
-#include <utils/Log.h>
-
-#include <fcntl.h>
-
-namespace android::jpegrecoverymap {
-
-#define VALID_IMAGE "/sdcard/Documents/minnie-320x240.yu12"
-#define VALID_IMAGE_WIDTH 320
-#define VALID_IMAGE_HEIGHT 240
-#define SINGLE_CHANNEL_IMAGE "/sdcard/Documents/minnie-320x240.y"
-#define SINGLE_CHANNEL_IMAGE_WIDTH VALID_IMAGE_WIDTH
-#define SINGLE_CHANNEL_IMAGE_HEIGHT VALID_IMAGE_HEIGHT
-#define INVALID_SIZE_IMAGE "/sdcard/Documents/minnie-318x240.yu12"
-#define INVALID_SIZE_IMAGE_WIDTH 318
-#define INVALID_SIZE_IMAGE_HEIGHT 240
-#define JPEG_QUALITY 90
-
-class JpegEncoderHelperTest : public testing::Test {
-public:
-    struct Image {
-        std::unique_ptr<uint8_t[]> buffer;
-        size_t width;
-        size_t height;
-    };
-    JpegEncoderHelperTest();
-    ~JpegEncoderHelperTest();
-protected:
-    virtual void SetUp();
-    virtual void TearDown();
-
-    Image mValidImage, mInvalidSizeImage, mSingleChannelImage;
-};
-
-JpegEncoderHelperTest::JpegEncoderHelperTest() {}
-
-JpegEncoderHelperTest::~JpegEncoderHelperTest() {}
-
-static size_t getFileSize(int fd) {
-    struct stat st;
-    if (fstat(fd, &st) < 0) {
-        ALOGW("%s : fstat failed", __func__);
-        return 0;
-    }
-    return st.st_size; // bytes
-}
-
-static bool loadFile(const char filename[], JpegEncoderHelperTest::Image* result) {
-    int fd = open(filename, O_CLOEXEC);
-    if (fd < 0) {
-        return false;
-    }
-    int length = getFileSize(fd);
-    if (length == 0) {
-        close(fd);
-        return false;
-    }
-    result->buffer.reset(new uint8_t[length]);
-    if (read(fd, result->buffer.get(), length) != static_cast<ssize_t>(length)) {
-        close(fd);
-        return false;
-    }
-    close(fd);
-    return true;
-}
-
-void JpegEncoderHelperTest::SetUp() {
-    if (!loadFile(VALID_IMAGE, &mValidImage)) {
-        FAIL() << "Load file " << VALID_IMAGE << " failed";
-    }
-    mValidImage.width = VALID_IMAGE_WIDTH;
-    mValidImage.height = VALID_IMAGE_HEIGHT;
-    if (!loadFile(INVALID_SIZE_IMAGE, &mInvalidSizeImage)) {
-        FAIL() << "Load file " << INVALID_SIZE_IMAGE << " failed";
-    }
-    mInvalidSizeImage.width = INVALID_SIZE_IMAGE_WIDTH;
-    mInvalidSizeImage.height = INVALID_SIZE_IMAGE_HEIGHT;
-    if (!loadFile(SINGLE_CHANNEL_IMAGE, &mSingleChannelImage)) {
-        FAIL() << "Load file " << SINGLE_CHANNEL_IMAGE << " failed";
-    }
-    mSingleChannelImage.width = SINGLE_CHANNEL_IMAGE_WIDTH;
-    mSingleChannelImage.height = SINGLE_CHANNEL_IMAGE_HEIGHT;
-}
-
-void JpegEncoderHelperTest::TearDown() {}
-
-TEST_F(JpegEncoderHelperTest, validImage) {
-    JpegEncoderHelper encoder;
-    EXPECT_TRUE(encoder.compressImage(mValidImage.buffer.get(), mValidImage.width,
-                                         mValidImage.height, JPEG_QUALITY, NULL, 0));
-    ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
-}
-
-TEST_F(JpegEncoderHelperTest, invalidSizeImage) {
-    JpegEncoderHelper encoder;
-    EXPECT_FALSE(encoder.compressImage(mInvalidSizeImage.buffer.get(), mInvalidSizeImage.width,
-                                          mInvalidSizeImage.height, JPEG_QUALITY, NULL, 0));
-}
-
-TEST_F(JpegEncoderHelperTest, singleChannelImage) {
-    JpegEncoderHelper encoder;
-    EXPECT_TRUE(encoder.compressImage(mSingleChannelImage.buffer.get(), mSingleChannelImage.width,
-                                         mSingleChannelImage.height, JPEG_QUALITY, NULL, 0, true));
-    ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
-}
-
-}  // namespace android::jpegrecoverymap
-
diff --git a/libs/nativedisplay/AChoreographer.cpp b/libs/nativedisplay/AChoreographer.cpp
index 66a40f1..8f005a5 100644
--- a/libs/nativedisplay/AChoreographer.cpp
+++ b/libs/nativedisplay/AChoreographer.cpp
@@ -197,7 +197,7 @@
             AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data);
     LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(),
                         "Data is only valid in callback");
-    return VsyncEventData::kFrameTimelinesLength;
+    return frameCallbackData->vsyncEventData.frameTimelinesLength;
 }
 size_t AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex(
         const AChoreographerFrameCallbackData* data) {
@@ -213,7 +213,7 @@
             AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data);
     LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(),
                         "Data is only valid in callback");
-    LOG_ALWAYS_FATAL_IF(index >= VsyncEventData::kFrameTimelinesLength, "Index out of bounds");
+    LOG_ALWAYS_FATAL_IF(index >= VsyncEventData::kFrameTimelinesCapacity, "Index out of bounds");
     return frameCallbackData->vsyncEventData.frameTimelines[index].vsyncId;
 }
 int64_t AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTimeNanos(
@@ -222,7 +222,7 @@
             AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data);
     LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(),
                         "Data is only valid in callback");
-    LOG_ALWAYS_FATAL_IF(index >= VsyncEventData::kFrameTimelinesLength, "Index out of bounds");
+    LOG_ALWAYS_FATAL_IF(index >= VsyncEventData::kFrameTimelinesCapacity, "Index out of bounds");
     return frameCallbackData->vsyncEventData.frameTimelines[index].expectedPresentationTime;
 }
 int64_t AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos(
@@ -231,7 +231,7 @@
             AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data);
     LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(),
                         "Data is only valid in callback");
-    LOG_ALWAYS_FATAL_IF(index >= VsyncEventData::kFrameTimelinesLength, "Index out of bounds");
+    LOG_ALWAYS_FATAL_IF(index >= VsyncEventData::kFrameTimelinesCapacity, "Index out of bounds");
     return frameCallbackData->vsyncEventData.frameTimelines[index].deadlineTimestamp;
 }
 
diff --git a/libs/nativewindow/AHardwareBuffer.cpp b/libs/nativewindow/AHardwareBuffer.cpp
index cf927db..8060705 100644
--- a/libs/nativewindow/AHardwareBuffer.cpp
+++ b/libs/nativewindow/AHardwareBuffer.cpp
@@ -18,6 +18,7 @@
 
 #include <android/hardware_buffer.h>
 #include <android/hardware_buffer_aidl.h>
+#include <android/binder_libbinder.h>
 #include <vndk/hardware_buffer.h>
 
 #include <errno.h>
@@ -34,9 +35,6 @@
 #include <android/hardware/graphics/common/1.1/types.h>
 #include <aidl/android/hardware/graphics/common/PixelFormat.h>
 
-// TODO: Better way to handle this
-#include "../binder/ndk/parcel_internal.h"
-
 static constexpr int kFdBufferSize = 128 * sizeof(int);  // 128 ints
 
 using namespace android;
@@ -421,7 +419,7 @@
         AHardwareBuffer* _Nullable* _Nonnull outBuffer) {
     if (!parcel || !outBuffer) return STATUS_BAD_VALUE;
     auto buffer = sp<GraphicBuffer>::make();
-    status_t status = parcel->get()->read(*buffer);
+    status_t status = AParcel_viewPlatformParcel(parcel)->read(*buffer);
     if (status != STATUS_OK) return status;
     *outBuffer = AHardwareBuffer_from_GraphicBuffer(buffer.get());
     AHardwareBuffer_acquire(*outBuffer);
@@ -433,7 +431,7 @@
     const GraphicBuffer* gb = AHardwareBuffer_to_GraphicBuffer(buffer);
     if (!gb) return STATUS_BAD_VALUE;
     if (!parcel) return STATUS_BAD_VALUE;
-    return parcel->get()->write(*gb);
+    return AParcel_viewPlatformParcel(parcel)->write(*gb);
 }
 
 // ----------------------------------------------------------------------------
diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp
index 5306529..dd5958d 100644
--- a/libs/nativewindow/ANativeWindow.cpp
+++ b/libs/nativewindow/ANativeWindow.cpp
@@ -79,27 +79,6 @@
     return res < 0 ? res : value;
 }
 
-static bool isDataSpaceValid(ANativeWindow* window, int32_t dataSpace) {
-    bool supported = false;
-    switch (dataSpace) {
-        case HAL_DATASPACE_UNKNOWN:
-        case HAL_DATASPACE_V0_SRGB:
-            return true;
-        // These data space need wide gamut support.
-        case HAL_DATASPACE_V0_SCRGB_LINEAR:
-        case HAL_DATASPACE_V0_SCRGB:
-        case HAL_DATASPACE_DISPLAY_P3:
-            native_window_get_wide_color_support(window, &supported);
-            return supported;
-        // These data space need HDR support.
-        case HAL_DATASPACE_BT2020_PQ:
-            native_window_get_hdr_support(window, &supported);
-            return supported;
-        default:
-            return false;
-    }
-}
-
 /**************************************************************************************************
  * NDK
  **************************************************************************************************/
@@ -219,8 +198,7 @@
     static_assert(static_cast<int>(ADATASPACE_DEPTH) == static_cast<int>(HAL_DATASPACE_DEPTH));
     static_assert(static_cast<int>(ADATASPACE_DYNAMIC_DEPTH) == static_cast<int>(HAL_DATASPACE_DYNAMIC_DEPTH));
 
-    if (!window || !query(window, NATIVE_WINDOW_IS_VALID) ||
-            !isDataSpaceValid(window, dataSpace)) {
+    if (!window || !query(window, NATIVE_WINDOW_IS_VALID)) {
         return -EINVAL;
     }
     return native_window_set_buffers_data_space(window,
diff --git a/libs/nativewindow/include/android/hardware_buffer.h b/libs/nativewindow/include/android/hardware_buffer.h
index 85a5249..21798d0 100644
--- a/libs/nativewindow/include/android/hardware_buffer.h
+++ b/libs/nativewindow/include/android/hardware_buffer.h
@@ -177,14 +177,14 @@
     /**
      * Corresponding formats:
      *   Vulkan: VK_FORMAT_R16_UINT
-     *   OpenGL ES: GR_GL_R16UI
+     *   OpenGL ES: GL_R16UI
      */
     AHARDWAREBUFFER_FORMAT_R16_UINT                 = 0x39,
 
     /**
      * Corresponding formats:
      *   Vulkan: VK_FORMAT_R16G16_UINT
-     *   OpenGL ES: GR_GL_RG16UI
+     *   OpenGL ES: GL_RG16UI
      */
     AHARDWAREBUFFER_FORMAT_R16G16_UINT              = 0x3a,
 
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index 6c54635..0fee3c1 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -1066,13 +1066,12 @@
                            (int)compatibility, (int)changeFrameRateStrategy);
 }
 
-static inline int native_window_set_frame_timeline_info(struct ANativeWindow* window,
-                                                        uint64_t frameNumber,
-                                                        int64_t frameTimelineVsyncId,
-                                                        int32_t inputEventId,
-                                                        int64_t startTimeNanos) {
+static inline int native_window_set_frame_timeline_info(
+        struct ANativeWindow* window, uint64_t frameNumber, int64_t frameTimelineVsyncId,
+        int32_t inputEventId, int64_t startTimeNanos, int32_t useForRefreshRateSelection) {
     return window->perform(window, NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO, frameNumber,
-                           frameTimelineVsyncId, inputEventId, startTimeNanos);
+                           frameTimelineVsyncId, inputEventId, startTimeNanos,
+                           useForRefreshRateSelection);
 }
 
 // ------------------------------------------------------------------------------------------------
diff --git a/libs/permission/AppOpsManager.cpp b/libs/permission/AppOpsManager.cpp
index baa9d75..6959274 100644
--- a/libs/permission/AppOpsManager.cpp
+++ b/libs/permission/AppOpsManager.cpp
@@ -146,6 +146,14 @@
     }
 }
 
+void AppOpsManager::startWatchingMode(int32_t op, const String16& packageName, int32_t flags,
+        const sp<IAppOpsCallback>& callback) {
+    sp<IAppOpsService> service = getService();
+    if (service != nullptr) {
+        service->startWatchingModeWithFlags(op, packageName, flags, callback);
+    }
+}
+
 void AppOpsManager::stopWatchingMode(const sp<IAppOpsCallback>& callback) {
     sp<IAppOpsService> service = getService();
     if (service != nullptr) {
diff --git a/libs/permission/IAppOpsService.cpp b/libs/permission/IAppOpsService.cpp
index d59f445..7f235a4 100644
--- a/libs/permission/IAppOpsService.cpp
+++ b/libs/permission/IAppOpsService.cpp
@@ -166,6 +166,17 @@
         }
         return reply.readBool();
     }
+
+    virtual void startWatchingModeWithFlags(int32_t op, const String16& packageName,
+        int32_t flags, const sp<IAppOpsCallback>& callback) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IAppOpsService::getInterfaceDescriptor());
+        data.writeInt32(op);
+        data.writeString16(packageName);
+        data.writeInt32(flags);
+        data.writeStrongBinder(IInterface::asBinder(callback));
+        remote()->transact(START_WATCHING_MODE_WITH_FLAGS_TRANSACTION, data, &reply);
+    }
 };
 
 IMPLEMENT_META_INTERFACE(AppOpsService, "com.android.internal.app.IAppOpsService")
diff --git a/libs/permission/include/binder/AppOpsManager.h b/libs/permission/include/binder/AppOpsManager.h
index abcd527..243532b 100644
--- a/libs/permission/include/binder/AppOpsManager.h
+++ b/libs/permission/include/binder/AppOpsManager.h
@@ -151,6 +151,10 @@
         _NUM_OP = 117
     };
 
+    enum {
+        WATCH_FOREGROUND_CHANGES = 1 << 0
+    };
+
     AppOpsManager();
 
     int32_t checkOp(int32_t op, int32_t uid, const String16& callingPackage);
@@ -174,6 +178,8 @@
             const std::optional<String16>& attributionTag);
     void startWatchingMode(int32_t op, const String16& packageName,
             const sp<IAppOpsCallback>& callback);
+    void startWatchingMode(int32_t op, const String16& packageName, int32_t flags,
+            const sp<IAppOpsCallback>& callback);
     void stopWatchingMode(const sp<IAppOpsCallback>& callback);
     int32_t permissionToOpCode(const String16& permission);
     void setCameraAudioRestriction(int32_t mode);
diff --git a/libs/permission/include/binder/IAppOpsService.h b/libs/permission/include/binder/IAppOpsService.h
index 22f056b..918fcdb 100644
--- a/libs/permission/include/binder/IAppOpsService.h
+++ b/libs/permission/include/binder/IAppOpsService.h
@@ -52,6 +52,8 @@
             const String16& packageName) = 0;
     virtual void setCameraAudioRestriction(int32_t mode) = 0;
     virtual bool shouldCollectNotes(int32_t opCode) = 0;
+    virtual void startWatchingModeWithFlags(int32_t op, const String16& packageName,
+            int32_t flags, const sp<IAppOpsCallback>& callback) = 0;
 
     enum {
         CHECK_OPERATION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
@@ -64,6 +66,7 @@
         CHECK_AUDIO_OPERATION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+7,
         SHOULD_COLLECT_NOTES_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+8,
         SET_CAMERA_AUDIO_RESTRICTION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+9,
+        START_WATCHING_MODE_WITH_FLAGS_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+10,
     };
 
     enum {
diff --git a/libs/renderengine/ExternalTexture.cpp b/libs/renderengine/ExternalTexture.cpp
index 210dca5..9eb42cd 100644
--- a/libs/renderengine/ExternalTexture.cpp
+++ b/libs/renderengine/ExternalTexture.cpp
@@ -14,17 +14,17 @@
  * limitations under the License.
  */
 
+#include <log/log.h>
 #include <renderengine/RenderEngine.h>
 #include <renderengine/impl/ExternalTexture.h>
 #include <ui/GraphicBuffer.h>
-
-#include "log/log_main.h"
+#include <utils/Trace.h>
 
 namespace android::renderengine::impl {
 
 ExternalTexture::ExternalTexture(const sp<GraphicBuffer>& buffer,
                                  renderengine::RenderEngine& renderEngine, uint32_t usage)
-      : mBuffer(buffer), mRenderEngine(renderEngine) {
+      : mBuffer(buffer), mRenderEngine(renderEngine), mWritable(usage & WRITEABLE) {
     LOG_ALWAYS_FATAL_IF(buffer == nullptr,
                         "Attempted to bind a null buffer to an external texture!");
     // GLESRenderEngine has a separate texture cache for output buffers,
@@ -35,11 +35,20 @@
                  renderengine::RenderEngine::RenderEngineType::THREADED)) {
         return;
     }
-    mRenderEngine.mapExternalTextureBuffer(mBuffer, usage & WRITEABLE);
+    mRenderEngine.mapExternalTextureBuffer(mBuffer, mWritable);
 }
 
 ExternalTexture::~ExternalTexture() {
     mRenderEngine.unmapExternalTextureBuffer(std::move(mBuffer));
 }
 
+void ExternalTexture::remapBuffer() {
+    ATRACE_CALL();
+    {
+        auto buf = mBuffer;
+        mRenderEngine.unmapExternalTextureBuffer(std::move(buf));
+    }
+    mRenderEngine.mapExternalTextureBuffer(mBuffer, mWritable);
+}
+
 } // namespace android::renderengine::impl
diff --git a/libs/renderengine/include/renderengine/ExternalTexture.h b/libs/renderengine/include/renderengine/ExternalTexture.h
index 621a209..82e5d83 100644
--- a/libs/renderengine/include/renderengine/ExternalTexture.h
+++ b/libs/renderengine/include/renderengine/ExternalTexture.h
@@ -46,6 +46,8 @@
     // Retrieves the buffer that is bound to this texture.
     virtual const sp<GraphicBuffer>& getBuffer() const = 0;
 
+    virtual void remapBuffer() = 0;
+
     Rect getBounds() const {
         return {0, 0, static_cast<int32_t>(getWidth()), static_cast<int32_t>(getHeight())};
     }
diff --git a/libs/renderengine/include/renderengine/impl/ExternalTexture.h b/libs/renderengine/include/renderengine/impl/ExternalTexture.h
index c0e24f0..d30262d 100644
--- a/libs/renderengine/include/renderengine/impl/ExternalTexture.h
+++ b/libs/renderengine/include/renderengine/impl/ExternalTexture.h
@@ -51,10 +51,12 @@
     bool hasSameBuffer(const renderengine::ExternalTexture& other) const override {
         return getBuffer() == other.getBuffer();
     }
+    void remapBuffer() override;
 
 private:
     sp<GraphicBuffer> mBuffer;
     android::renderengine::RenderEngine& mRenderEngine;
+    const bool mWritable;
 };
 
 } // namespace android::renderengine::impl
diff --git a/libs/renderengine/include/renderengine/mock/FakeExternalTexture.h b/libs/renderengine/include/renderengine/mock/FakeExternalTexture.h
index b95f011..474e2e7 100644
--- a/libs/renderengine/include/renderengine/mock/FakeExternalTexture.h
+++ b/libs/renderengine/include/renderengine/mock/FakeExternalTexture.h
@@ -45,6 +45,7 @@
     uint64_t getId() const override { return mId; }
     PixelFormat getPixelFormat() const override { return mPixelFormat; }
     uint64_t getUsage() const override { return mUsage; }
+    void remapBuffer() override {}
     ~FakeExternalTexture() = default;
 };
 
diff --git a/libs/renderengine/skia/AutoBackendTexture.cpp b/libs/renderengine/skia/AutoBackendTexture.cpp
index 5c122d4..c412c9c 100644
--- a/libs/renderengine/skia/AutoBackendTexture.cpp
+++ b/libs/renderengine/skia/AutoBackendTexture.cpp
@@ -43,10 +43,12 @@
                                                        createProtectedImage, backendFormat,
                                                        isOutputBuffer);
     mColorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format);
-    ALOGE_IF(!mBackendTexture.isValid(),
-             "Failed to create a valid texture. [%p]:[%d,%d] isProtected:%d isWriteable:%d "
-             "format:%d",
-             this, desc.width, desc.height, createProtectedImage, isOutputBuffer, desc.format);
+    if (!mBackendTexture.isValid() || !desc.width || !desc.height) {
+        LOG_ALWAYS_FATAL("Failed to create a valid texture. [%p]:[%d,%d] isProtected:%d "
+                         "isWriteable:%d format:%d",
+                         this, desc.width, desc.height, createProtectedImage, isOutputBuffer,
+                         desc.format);
+    }
 }
 
 AutoBackendTexture::~AutoBackendTexture() {
@@ -82,6 +84,18 @@
     textureRelease->unref(false);
 }
 
+void logFatalTexture(const char* msg, const GrBackendTexture& tex, ui::Dataspace dataspace,
+                     SkColorType colorType) {
+    GrGLTextureInfo textureInfo;
+    bool retrievedTextureInfo = tex.getGLTextureInfo(&textureInfo);
+    LOG_ALWAYS_FATAL("%s isTextureValid:%d dataspace:%d"
+                     "\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i texType: %i"
+                     "\n\t\tGrGLTextureInfo: success: %i fTarget: %u fFormat: %u colorType %i",
+                     msg, tex.isValid(), dataspace, tex.width(), tex.height(), tex.hasMipmaps(),
+                     tex.isProtected(), static_cast<int>(tex.textureType()), retrievedTextureInfo,
+                     textureInfo.fTarget, textureInfo.fFormat, colorType);
+}
+
 sk_sp<SkImage> AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaType alphaType,
                                              GrDirectContext* context) {
     ATRACE_CALL();
@@ -107,9 +121,9 @@
 
     mImage = image;
     mDataspace = dataspace;
-    LOG_ALWAYS_FATAL_IF(mImage == nullptr,
-                        "Unable to generate SkImage. isTextureValid:%d dataspace:%d",
-                        mBackendTexture.isValid(), dataspace);
+    if (!mImage) {
+        logFatalTexture("Unable to generate SkImage.", mBackendTexture, dataspace, colorType);
+    }
     return mImage;
 }
 
@@ -131,9 +145,9 @@
     }
 
     mDataspace = dataspace;
-    LOG_ALWAYS_FATAL_IF(mSurface == nullptr,
-                        "Unable to generate SkSurface. isTextureValid:%d dataspace:%d",
-                        mBackendTexture.isValid(), dataspace);
+    if (!mSurface) {
+        logFatalTexture("Unable to generate SkSurface.", mBackendTexture, dataspace, mColorType);
+    }
     return mSurface;
 }
 
diff --git a/libs/renderengine/skia/ColorSpaces.cpp b/libs/renderengine/skia/ColorSpaces.cpp
index 37ff5df..92b01e0 100644
--- a/libs/renderengine/skia/ColorSpaces.cpp
+++ b/libs/renderengine/skia/ColorSpaces.cpp
@@ -21,6 +21,8 @@
 namespace skia {
 
 // please keep in sync with hwui/utils/Color.cpp
+// TODO: Scale by the dimming ratio here instead of in a generic 3x3 transform
+// Otherwise there may be luminance shift for e.g., HLG.
 sk_sp<SkColorSpace> toSkColorSpace(ui::Dataspace dataspace) {
     skcms_Matrix3x3 gamut;
     switch (dataspace & HAL_DATASPACE_STANDARD_MASK) {
@@ -61,13 +63,14 @@
         case HAL_DATASPACE_TRANSFER_GAMMA2_8:
             return SkColorSpace::MakeRGB({2.8f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut);
         case HAL_DATASPACE_TRANSFER_ST2084:
-            return SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, gamut);
+            return SkColorSpace::MakeRGB({-2.f, -1.55522297832f, 1.86045365631f, 32 / 2523.0f,
+                                          2413 / 128.0f, -2392 / 128.0f, 8192 / 1305.0f},
+                                         gamut);
         case HAL_DATASPACE_TRANSFER_SMPTE_170M:
             return SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, gamut);
         case HAL_DATASPACE_TRANSFER_HLG:
-            // return HLG transfer but scale by 1/12
             skcms_TransferFunction hlgFn;
-            if (skcms_TransferFunction_makeScaledHLGish(&hlgFn, 1.f / 12.f, 2.f, 2.f,
+            if (skcms_TransferFunction_makeScaledHLGish(&hlgFn, 0.314509843, 2.f, 2.f,
                                                         1.f / 0.17883277f, 0.28466892f,
                                                         0.55991073f)) {
                 return SkColorSpace::MakeRGB(hlgFn, gamut);
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index 5965d41..5854135 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -39,10 +39,10 @@
 #include <SkPath.h>
 #include <SkPoint.h>
 #include <SkPoint3.h>
+#include <SkRRect.h>
 #include <SkRect.h>
 #include <SkRefCnt.h>
 #include <SkRegion.h>
-#include <SkRRect.h>
 #include <SkRuntimeEffect.h>
 #include <SkSamplingOptions.h>
 #include <SkScalar.h>
@@ -51,9 +51,11 @@
 #include <SkString.h>
 #include <SkSurface.h>
 #include <SkTileMode.h>
-#include <src/core/SkTraceEventCommon.h>
 #include <android-base/stringprintf.h>
+#include <gui/FenceMonitor.h>
 #include <gui/TraceUtils.h>
+#include <pthread.h>
+#include <src/core/SkTraceEventCommon.h>
 #include <sync/sync.h>
 #include <ui/BlurRegion.h>
 #include <ui/DataspaceUtils.h>
@@ -63,6 +65,7 @@
 
 #include <cmath>
 #include <cstdint>
+#include <deque>
 #include <memory>
 #include <numeric>
 
@@ -229,7 +232,6 @@
 static inline SkPoint3 getSkPoint3(const android::vec3& vector) {
     return SkPoint3::Make(vector.x, vector.y, vector.z);
 }
-
 } // namespace
 
 namespace android {
@@ -264,14 +266,11 @@
     SkAndroidFrameworkTraceUtil::setEnableTracing(tracingEnabled);
 }
 
-SkiaRenderEngine::SkiaRenderEngine(
-    RenderEngineType type,
-    PixelFormat pixelFormat,
-    bool useColorManagement,
-    bool supportsBackgroundBlur) :
-    RenderEngine(type),
-    mDefaultPixelFormat(pixelFormat),
-    mUseColorManagement(useColorManagement) {
+SkiaRenderEngine::SkiaRenderEngine(RenderEngineType type, PixelFormat pixelFormat,
+                                   bool useColorManagement, bool supportsBackgroundBlur)
+      : RenderEngine(type),
+        mDefaultPixelFormat(pixelFormat),
+        mUseColorManagement(useColorManagement) {
     if (supportsBackgroundBlur) {
         ALOGD("Background Blurs Enabled");
         mBlurFilter = new KawaseBlurFilter();
@@ -395,10 +394,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 +464,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();
@@ -489,15 +506,9 @@
     }
 
     if (parameters.requiresLinearEffect) {
-        const ui::Dataspace inputDataspace = mUseColorManagement ? parameters.layer.sourceDataspace
-                                                                 : ui::Dataspace::V0_SRGB_LINEAR;
-        const ui::Dataspace outputDataspace = mUseColorManagement
-                ? parameters.display.outputDataspace
-                : ui::Dataspace::V0_SRGB_LINEAR;
-
         auto effect =
-                shaders::LinearEffect{.inputDataspace = inputDataspace,
-                                      .outputDataspace = outputDataspace,
+                shaders::LinearEffect{.inputDataspace = parameters.layer.sourceDataspace,
+                                      .outputDataspace = parameters.outputDataSpace,
                                       .undoPremultipliedAlpha = parameters.undoPremultipliedAlpha};
 
         auto effectIter = mRuntimeEffects.find(effect);
@@ -508,16 +519,18 @@
         } else {
             runtimeEffect = effectIter->second;
         }
+
         mat4 colorTransform = parameters.layer.colorTransform;
 
         colorTransform *=
                 mat4::scale(vec4(parameters.layerDimmingRatio, parameters.layerDimmingRatio,
                                  parameters.layerDimmingRatio, 1.f));
+
         const auto targetBuffer = parameters.layer.source.buffer.buffer;
         const auto graphicBuffer = targetBuffer ? targetBuffer->getBuffer() : nullptr;
         const auto hardwareBuffer = graphicBuffer ? graphicBuffer->toAHardwareBuffer() : nullptr;
-        return createLinearEffectShader(parameters.shader, effect, runtimeEffect, colorTransform,
-                                        parameters.display.maxLuminance,
+        return createLinearEffectShader(parameters.shader, effect, runtimeEffect,
+                                        std::move(colorTransform), parameters.display.maxLuminance,
                                         parameters.display.currentLuminanceNits,
                                         parameters.layer.source.buffer.maxLuminanceNits,
                                         hardwareBuffer, parameters.display.renderIntent);
@@ -651,28 +664,17 @@
     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);
 
-    const ui::Dataspace dstDataspace =
-            mUseColorManagement ? display.outputDataspace : ui::Dataspace::V0_SRGB_LINEAR;
-    sk_sp<SkSurface> dstSurface = surfaceTextureRef->getOrCreateSurface(dstDataspace, grContext);
+    sk_sp<SkSurface> dstSurface =
+            surfaceTextureRef->getOrCreateSurface(display.outputDataspace, grContext);
 
     SkCanvas* dstCanvas = mCapture->tryCapture(dstSurface.get());
     if (dstCanvas == nullptr) {
@@ -880,10 +882,31 @@
         const bool dimInLinearSpace = display.dimmingStage !=
                 aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF;
 
+        const bool isExtendedHdr = (layer.sourceDataspace & ui::Dataspace::RANGE_MASK) ==
+                        static_cast<int32_t>(ui::Dataspace::RANGE_EXTENDED) &&
+                (display.outputDataspace & ui::Dataspace::TRANSFER_MASK) ==
+                        static_cast<int32_t>(ui::Dataspace::TRANSFER_SRGB);
+
+        const ui::Dataspace runtimeEffectDataspace = !dimInLinearSpace && isExtendedHdr
+                ? static_cast<ui::Dataspace>(
+                          (display.outputDataspace & ui::Dataspace::STANDARD_MASK) |
+                          ui::Dataspace::TRANSFER_GAMMA2_2 |
+                          (display.outputDataspace & ui::Dataspace::RANGE_MASK))
+                : display.outputDataspace;
+
+        // If the input dataspace is range extended, the output dataspace transfer is sRGB
+        // and dimmingStage is GAMMA_OETF, dim in linear space instead, and
+        // set the output dataspace's transfer to be GAMMA2_2.
+        // This allows DPU side to use oetf_gamma_2p2 for extended HDR layer
+        // to avoid tone shift.
+        // The reason of tone shift here is because HDR layers manage white point
+        // luminance in linear space, which color pipelines request GAMMA_OETF break
+        // without a gamma 2.2 fixup.
         const bool requiresLinearEffect = layer.colorTransform != mat4() ||
                 (mUseColorManagement &&
                  needsToneMapping(layer.sourceDataspace, display.outputDataspace)) ||
-                (dimInLinearSpace && !equalsWithinMargin(1.f, layerDimmingRatio));
+                (dimInLinearSpace && !equalsWithinMargin(1.f, layerDimmingRatio)) ||
+                (!dimInLinearSpace && isExtendedHdr);
 
         // quick abort from drawing the remaining portion of the layer
         if (layer.skipContentDraw ||
@@ -892,33 +915,17 @@
             continue;
         }
 
-        // If we need to map to linear space or color management is disabled, then mark the source
-        // image with the same colorspace as the destination surface so that Skia's color
-        // management is a no-op.
-        const ui::Dataspace layerDataspace = (!mUseColorManagement || requiresLinearEffect)
-                ? dstDataspace
-                : layer.sourceDataspace;
+        // If color management is disabled, then mark the source image with the same colorspace as
+        // the destination surface so that Skia's color management is a no-op.
+        const ui::Dataspace layerDataspace =
+                !mUseColorManagement ? display.outputDataspace : layer.sourceDataspace;
 
         SkPaint paint;
         if (layer.source.buffer.buffer) {
             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.
@@ -991,7 +998,8 @@
                                                   .requiresLinearEffect = requiresLinearEffect,
                                                   .layerDimmingRatio = dimInLinearSpace
                                                           ? layerDimmingRatio
-                                                          : 1.f}));
+                                                          : 1.f,
+                                                  .outputDataSpace = runtimeEffectDataspace}));
 
             // Turn on dithering when dimming beyond this (arbitrary) threshold...
             static constexpr float kDimmingThreshold = 0.2f;
@@ -1054,7 +1062,8 @@
                                                   .display = display,
                                                   .undoPremultipliedAlpha = false,
                                                   .requiresLinearEffect = requiresLinearEffect,
-                                                  .layerDimmingRatio = layerDimmingRatio}));
+                                                  .layerDimmingRatio = layerDimmingRatio,
+                                                  .outputDataSpace = runtimeEffectDataspace}));
         }
 
         if (layer.disableBlending) {
@@ -1127,8 +1136,13 @@
         activeSurface->flush();
     }
 
-    base::unique_fd drawFence = flushAndSubmit(grContext);
-    resultPromise->set_value(sp<Fence>::make(std::move(drawFence)));
+    auto drawFence = sp<Fence>::make(flushAndSubmit(grContext));
+
+    if (ATRACE_ENABLED()) {
+        static gui::FenceMonitor sMonitor("RE Completion");
+        sMonitor.queueFence(drawFence);
+    }
+    resultPromise->set_value(std::move(drawFence));
 }
 
 size_t SkiaRenderEngine::getMaxTextureSize() const {
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index dd6646b..6457bfa 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);
@@ -154,6 +156,7 @@
         bool undoPremultipliedAlpha;
         bool requiresLinearEffect;
         float layerDimmingRatio;
+        const ui::Dataspace outputDataSpace;
     };
     sk_sp<SkShader> createRuntimeEffectShader(const RuntimeEffectShaderParameters&);
 
@@ -167,7 +170,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/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
index 936e316..b99e385 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -52,6 +52,20 @@
     PFN_vkDestroyInstance vkDestroyInstance = nullptr;
 };
 
+// Ref-Count a semaphore
+struct DestroySemaphoreInfo {
+    VkSemaphore mSemaphore;
+    // We need to make sure we don't delete the VkSemaphore until it is done being used by both Skia
+    // (including by the GPU) and inside SkiaVkRenderEngine. So we always start with two refs, one
+    // owned by Skia and one owned by the SkiaVkRenderEngine. The refs are decremented each time
+    // delete_semaphore* is called with this object. Skia will call destroy_semaphore* once it is
+    // done with the semaphore and the GPU has finished work on the semaphore. SkiaVkRenderEngine
+    // calls delete_semaphore* after sending the semaphore to Skia and exporting it if need be.
+    int mRefs = 2;
+
+    DestroySemaphoreInfo(VkSemaphore semaphore) : mSemaphore(semaphore) {}
+};
+
 struct VulkanInterface {
     bool initialized = false;
     VkInstance instance;
@@ -588,14 +602,22 @@
     return true;
 }
 
-static void delete_semaphore(void* _semaphore) {
-    VkSemaphore semaphore = (VkSemaphore)_semaphore;
-    sVulkanInterface.destroySemaphore(semaphore);
+static void delete_semaphore(void* semaphore) {
+    DestroySemaphoreInfo* info = reinterpret_cast<DestroySemaphoreInfo*>(semaphore);
+    --info->mRefs;
+    if (!info->mRefs) {
+        sVulkanInterface.destroySemaphore(info->mSemaphore);
+        delete info;
+    }
 }
 
-static void delete_semaphore_protected(void* _semaphore) {
-    VkSemaphore semaphore = (VkSemaphore)_semaphore;
-    sProtectedContentVulkanInterface.destroySemaphore(semaphore);
+static void delete_semaphore_protected(void* semaphore) {
+    DestroySemaphoreInfo* info = reinterpret_cast<DestroySemaphoreInfo*>(semaphore);
+    --info->mRefs;
+    if (!info->mRefs) {
+        sProtectedContentVulkanInterface.destroySemaphore(info->mSemaphore);
+        delete info;
+    }
 }
 
 static VulkanInterface& getVulkanInterface(bool protectedContext) {
@@ -624,19 +646,30 @@
 }
 
 base::unique_fd SkiaVkRenderEngine::flushAndSubmit(GrDirectContext* grContext) {
-    VkSemaphore signalSemaphore = getVulkanInterface(isProtected()).createExportableSemaphore();
-    GrBackendSemaphore beSignalSemaphore;
-    beSignalSemaphore.initVulkan(signalSemaphore);
+    VulkanInterface& vi = getVulkanInterface(isProtected());
+    VkSemaphore semaphore = vi.createExportableSemaphore();
+
+    GrBackendSemaphore backendSemaphore;
+    backendSemaphore.initVulkan(semaphore);
+
     GrFlushInfo flushInfo;
-    flushInfo.fNumSemaphores = 1;
-    flushInfo.fSignalSemaphores = &beSignalSemaphore;
-    flushInfo.fFinishedProc = isProtected() ? delete_semaphore_protected : delete_semaphore;
-    flushInfo.fFinishedContext = (void*)signalSemaphore;
+    DestroySemaphoreInfo* destroySemaphoreInfo = nullptr;
+    if (semaphore != VK_NULL_HANDLE) {
+        destroySemaphoreInfo = new DestroySemaphoreInfo(semaphore);
+        flushInfo.fNumSemaphores = 1;
+        flushInfo.fSignalSemaphores = &backendSemaphore;
+        flushInfo.fFinishedProc = isProtected() ? delete_semaphore_protected : delete_semaphore;
+        flushInfo.fFinishedContext = destroySemaphoreInfo;
+    }
     GrSemaphoresSubmitted submitted = grContext->flush(flushInfo);
     grContext->submit(false /* no cpu sync */);
     int drawFenceFd = -1;
-    if (GrSemaphoresSubmitted::kYes == submitted) {
-        drawFenceFd = getVulkanInterface(isProtected()).exportSemaphoreSyncFd(signalSemaphore);
+    if (semaphore != VK_NULL_HANDLE) {
+        if (GrSemaphoresSubmitted::kYes == submitted) {
+            drawFenceFd = vi.exportSemaphoreSyncFd(semaphore);
+        }
+        // Now that drawFenceFd has been created, we can delete our reference to this semaphore
+        flushInfo.fFinishedProc(destroySemaphoreInfo);
     }
     base::unique_fd res(drawFenceFd);
     return res;
diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp
index ba190e0..980f8d1 100644
--- a/libs/sensor/SensorManager.cpp
+++ b/libs/sensor/SensorManager.cpp
@@ -176,11 +176,8 @@
 
         mSensors = mSensorServer->getSensorList(mOpPackageName);
         size_t count = mSensors.size();
-        if (count == 0) {
-            ALOGE("Failed to get Sensor list");
-            mSensorServer.clear();
-            return UNKNOWN_ERROR;
-        }
+        // If count is 0, mSensorList will be non-null. This is old
+        // existing behavior and callers expect this.
         mSensorList =
                 static_cast<Sensor const**>(malloc(count * sizeof(Sensor*)));
         LOG_ALWAYS_FATAL_IF(mSensorList == nullptr, "mSensorList NULL");
diff --git a/libs/shaders/include/shaders/shaders.h b/libs/shaders/include/shaders/shaders.h
index 42b0cc1..5a4aaab 100644
--- a/libs/shaders/include/shaders/shaders.h
+++ b/libs/shaders/include/shaders/shaders.h
@@ -51,23 +51,20 @@
     // Input dataspace of the source colors.
     const ui::Dataspace inputDataspace = ui::Dataspace::SRGB;
 
-    // Working dataspace for the output surface, for conversion from linear space.
+    // Working dataspace for the output surface.
     const ui::Dataspace outputDataspace = ui::Dataspace::SRGB;
 
     // Sets whether alpha premultiplication must be undone.
     // This is required if the source colors use premultiplied alpha and is not opaque.
     const bool undoPremultipliedAlpha = false;
 
-    // "Fake" dataspace of the source colors. This is used for applying an EOTF to compute linear
-    // RGB. This is used when Skia is expected to color manage the input image based on the
-    // dataspace of the provided source image and destination surface. SkRuntimeEffects use the
-    // destination color space as the working color space. RenderEngine deliberately sets the color
-    // space for input images and destination surfaces to be the same whenever LinearEffects are
-    // expected to be used so that color-management is controlled by RenderEngine, but other users
-    // of a LinearEffect may not be able to control the color space of the images and surfaces. So
-    // fakeInputDataspace is used to essentially masquerade the input dataspace to be the output
-    // dataspace for correct conversion to linear colors.
-    ui::Dataspace fakeInputDataspace = ui::Dataspace::UNKNOWN;
+    // "Fake" dataspace of the destination colors. This is used for applying an OETF to compute
+    // non-linear RGB. This is used when Skia is expected to color manage the input image based on
+    // the dataspace of the provided source image and destination surface. Some use-cases in
+    // RenderEngine expect to apply a different OETF than what is expected by Skia. As in,
+    // RenderEngine will color manage to a custom destination and "cast" the result to Skia's
+    // working space.
+    ui::Dataspace fakeOutputDataspace = ui::Dataspace::UNKNOWN;
 
     enum SkSLType { Shader, ColorFilter };
     SkSLType type = Shader;
@@ -76,7 +73,7 @@
 static inline bool operator==(const LinearEffect& lhs, const LinearEffect& rhs) {
     return lhs.inputDataspace == rhs.inputDataspace && lhs.outputDataspace == rhs.outputDataspace &&
             lhs.undoPremultipliedAlpha == rhs.undoPremultipliedAlpha &&
-            lhs.fakeInputDataspace == rhs.fakeInputDataspace;
+            lhs.fakeOutputDataspace == rhs.fakeOutputDataspace;
 }
 
 struct LinearEffectHasher {
@@ -89,7 +86,7 @@
         size_t result = std::hash<ui::Dataspace>{}(le.inputDataspace);
         result = HashCombine(result, std::hash<ui::Dataspace>{}(le.outputDataspace));
         result = HashCombine(result, std::hash<bool>{}(le.undoPremultipliedAlpha));
-        return HashCombine(result, std::hash<ui::Dataspace>{}(le.fakeInputDataspace));
+        return HashCombine(result, std::hash<ui::Dataspace>{}(le.fakeOutputDataspace));
     }
 };
 
@@ -99,10 +96,6 @@
 // 2. Apply color transform matrices in linear space
 std::string buildLinearEffectSkSL(const LinearEffect& linearEffect);
 
-// Generates a shader string that applies color transforms in linear space.
-// This is intended to be plugged into an SkColorFilter
-std::string buildLinearEffectSkSLForColorFilter(const LinearEffect& linearEffect);
-
 // Generates a list of uniforms to set on the LinearEffect shader above.
 std::vector<tonemap::ShaderUniform> buildLinearEffectUniforms(
         const LinearEffect& linearEffect, const mat4& colorTransform, float maxDisplayLuminance,
diff --git a/libs/shaders/shaders.cpp b/libs/shaders/shaders.cpp
index a3c403e..c85517a 100644
--- a/libs/shaders/shaders.cpp
+++ b/libs/shaders/shaders.cpp
@@ -33,212 +33,111 @@
     return static_cast<aidl::android::hardware::graphics::common::Dataspace>(dataspace);
 }
 
-void generateEOTF(ui::Dataspace dataspace, std::string& shader) {
-    switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) {
-        case HAL_DATASPACE_TRANSFER_ST2084:
-            shader.append(R"(
-
-                float3 EOTF(float3 color) {
-                    float m1 = (2610.0 / 4096.0) / 4.0;
-                    float m2 = (2523.0 / 4096.0) * 128.0;
-                    float c1 = (3424.0 / 4096.0);
-                    float c2 = (2413.0 / 4096.0) * 32.0;
-                    float c3 = (2392.0 / 4096.0) * 32.0;
-
-                    float3 tmp = pow(clamp(color, 0.0, 1.0), 1.0 / float3(m2));
-                    tmp = max(tmp - c1, 0.0) / (c2 - c3 * tmp);
-                    return pow(tmp, 1.0 / float3(m1));
-                }
-            )");
-            break;
-        case HAL_DATASPACE_TRANSFER_HLG:
-            shader.append(R"(
-                float EOTF_channel(float channel) {
-                    const float a = 0.17883277;
-                    const float b = 0.28466892;
-                    const float c = 0.55991073;
-                    return channel <= 0.5 ? channel * channel / 3.0 :
-                            (exp((channel - c) / a) + b) / 12.0;
-                }
-
-                float3 EOTF(float3 color) {
-                    return float3(EOTF_channel(color.r), EOTF_channel(color.g),
-                            EOTF_channel(color.b));
-                }
-            )");
-            break;
-        case HAL_DATASPACE_TRANSFER_LINEAR:
-            shader.append(R"(
-                float3 EOTF(float3 color) {
-                    return color;
-                }
-            )");
-            break;
-        case HAL_DATASPACE_TRANSFER_SMPTE_170M:
-            shader.append(R"(
-
-                float EOTF_sRGB(float srgb) {
-                    return srgb <= 0.08125 ? srgb / 4.50 : pow((srgb + 0.099) / 1.099, 1 / 0.45);
-                }
-
-                float3 EOTF_sRGB(float3 srgb) {
-                    return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b));
-                }
-
-                float3 EOTF(float3 srgb) {
-                    return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb));
-                }
-            )");
-            break;
-        case HAL_DATASPACE_TRANSFER_GAMMA2_2:
-            shader.append(R"(
-
-                float EOTF_sRGB(float srgb) {
-                    return pow(srgb, 2.2);
-                }
-
-                float3 EOTF_sRGB(float3 srgb) {
-                    return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b));
-                }
-
-                float3 EOTF(float3 srgb) {
-                    return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb));
-                }
-            )");
-            break;
-        case HAL_DATASPACE_TRANSFER_GAMMA2_6:
-            shader.append(R"(
-
-                float EOTF_sRGB(float srgb) {
-                    return pow(srgb, 2.6);
-                }
-
-                float3 EOTF_sRGB(float3 srgb) {
-                    return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b));
-                }
-
-                float3 EOTF(float3 srgb) {
-                    return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb));
-                }
-            )");
-            break;
-        case HAL_DATASPACE_TRANSFER_GAMMA2_8:
-            shader.append(R"(
-
-                float EOTF_sRGB(float srgb) {
-                    return pow(srgb, 2.8);
-                }
-
-                float3 EOTF_sRGB(float3 srgb) {
-                    return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b));
-                }
-
-                float3 EOTF(float3 srgb) {
-                    return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb));
-                }
-            )");
-            break;
-        case HAL_DATASPACE_TRANSFER_SRGB:
-        default:
-            shader.append(R"(
-
-                float EOTF_sRGB(float srgb) {
-                    return srgb <= 0.04045 ? srgb / 12.92 : pow((srgb + 0.055) / 1.055, 2.4);
-                }
-
-                float3 EOTF_sRGB(float3 srgb) {
-                    return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b));
-                }
-
-                float3 EOTF(float3 srgb) {
-                    return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb));
-                }
-            )");
-            break;
-    }
-}
-
 void generateXYZTransforms(std::string& shader) {
     shader.append(R"(
-        uniform float4x4 in_rgbToXyz;
-        uniform float4x4 in_xyzToRgb;
+        uniform float3x3 in_rgbToXyz;
+        uniform float3x3 in_xyzToSrcRgb;
+        uniform float4x4 in_colorTransform;
         float3 ToXYZ(float3 rgb) {
-            return (in_rgbToXyz * float4(rgb, 1.0)).rgb;
+            return in_rgbToXyz * rgb;
         }
 
-        float3 ToRGB(float3 xyz) {
-            return clamp((in_xyzToRgb * float4(xyz, 1.0)).rgb, 0.0, 1.0);
+        float3 ToSrcRGB(float3 xyz) {
+            return in_xyzToSrcRgb * xyz;
+        }
+
+        float3 ApplyColorTransform(float3 rgb) {
+            return (in_colorTransform * float4(rgb, 1.0)).rgb;
         }
     )");
 }
 
-// Conversion from relative light to absolute light (maps from [0, 1] to [0, maxNits])
-void generateLuminanceScalesForOOTF(ui::Dataspace inputDataspace, ui::Dataspace outputDataspace,
-                                    std::string& shader) {
+// Conversion from relative light to absolute light
+// Note that 1.0 == 203 nits.
+void generateLuminanceScalesForOOTF(ui::Dataspace inputDataspace, std::string& shader) {
     switch (inputDataspace & HAL_DATASPACE_TRANSFER_MASK) {
-        case HAL_DATASPACE_TRANSFER_ST2084:
-            shader.append(R"(
-                    float3 ScaleLuminance(float3 xyz) {
-                        return xyz * 10000.0;
-                    }
-                )");
-            break;
         case HAL_DATASPACE_TRANSFER_HLG:
+            // BT. 2408 says that a signal level of 0.75 == 203 nits for HLG, but that's after
+            // applying OOTF. But we haven't applied OOTF yet, so we need to scale by a different
+            // constant instead.
             shader.append(R"(
-                    float3 ScaleLuminance(float3 xyz) {
-                        return xyz * 1000.0;
-                    }
-                )");
+                float3 ScaleLuminance(float3 xyz) {
+                    return xyz * 264.96;
+                }
+            )");
             break;
         default:
-            switch (outputDataspace & HAL_DATASPACE_TRANSFER_MASK) {
-                case HAL_DATASPACE_TRANSFER_ST2084:
-                case HAL_DATASPACE_TRANSFER_HLG:
-                    // SDR -> HDR tonemap
-                    shader.append(R"(
-                            float3 ScaleLuminance(float3 xyz) {
-                                return xyz * in_libtonemap_inputMaxLuminance;
-                            }
-                        )");
-                    break;
-                default:
-                    // Input and output are both SDR, so no tone-mapping is expected so
-                    // no-op the luminance normalization.
-                    shader.append(R"(
-                                float3 ScaleLuminance(float3 xyz) {
-                                    return xyz * in_libtonemap_displayMaxLuminance;
-                                }
-                            )");
-                    break;
-            }
+            shader.append(R"(
+                float3 ScaleLuminance(float3 xyz) {
+                    return xyz * 203.0;
+                }
+            )");
+            break;
     }
 }
 
 // Normalizes from absolute light back to relative light (maps from [0, maxNits] back to [0, 1])
-static void generateLuminanceNormalizationForOOTF(ui::Dataspace outputDataspace,
+static void generateLuminanceNormalizationForOOTF(ui::Dataspace inputDataspace,
+                                                  ui::Dataspace outputDataspace,
                                                   std::string& shader) {
     switch (outputDataspace & HAL_DATASPACE_TRANSFER_MASK) {
         case HAL_DATASPACE_TRANSFER_ST2084:
             shader.append(R"(
-                    float3 NormalizeLuminance(float3 xyz) {
-                        return xyz / 10000.0;
-                    }
-                )");
+                float3 NormalizeLuminance(float3 xyz) {
+                    return xyz / 203.0;
+                }
+            )");
             break;
         case HAL_DATASPACE_TRANSFER_HLG:
-            shader.append(R"(
-                    float3 NormalizeLuminance(float3 xyz) {
-                        return xyz / 1000.0;
-                    }
-                )");
+            switch (inputDataspace & HAL_DATASPACE_TRANSFER_MASK) {
+                case HAL_DATASPACE_TRANSFER_HLG:
+                    shader.append(R"(
+                            float3 NormalizeLuminance(float3 xyz) {
+                                return xyz / 264.96;
+                            }
+                        )");
+                    break;
+                default:
+                    // Transcoding to HLG requires applying the inverse OOTF
+                    // with the expectation that the OOTF is then applied during
+                    // tonemapping downstream.
+                    // BT. 2100-2 operates on normalized luminances, so renormalize to the input to
+                    // correctly adjust gamma.
+                    // Note that following BT. 2408 for HLG OETF actually maps 0.75 == ~264.96 nits,
+                    // rather than 203 nits, because 203 nits == OOTF(invOETF(0.75)), so even though
+                    // we originally scaled by 203 nits we need to re-normalize to 264.96 nits when
+                    // converting to the correct brightness range.
+                    shader.append(R"(
+                            float3 NormalizeLuminance(float3 xyz) {
+                                float ootfGain = pow(xyz.y / 1000.0, -0.2 / 1.2);
+                                return xyz * ootfGain / 264.96;
+                            }
+                        )");
+                    break;
+            }
             break;
         default:
-            shader.append(R"(
-                    float3 NormalizeLuminance(float3 xyz) {
-                        return xyz / in_libtonemap_displayMaxLuminance;
-                    }
-                )");
-            break;
+            switch (inputDataspace & HAL_DATASPACE_TRANSFER_MASK) {
+                case HAL_DATASPACE_TRANSFER_HLG:
+                case HAL_DATASPACE_TRANSFER_ST2084:
+                    // libtonemap outputs a range [0, in_libtonemap_displayMaxLuminance], so
+                    // normalize back to [0, 1] when the output is SDR.
+                    shader.append(R"(
+                        float3 NormalizeLuminance(float3 xyz) {
+                            return xyz / in_libtonemap_displayMaxLuminance;
+                        }
+                    )");
+                    break;
+                default:
+                    // Otherwise normalize back down to the range [0, 1]
+                    // TODO: get this working for extended range outputs
+                    shader.append(R"(
+                        float3 NormalizeLuminance(float3 xyz) {
+                            return xyz / 203.0;
+                        }
+                    )");
+                    break;
+            }
     }
 }
 
@@ -249,145 +148,34 @@
                                                           toAidlDataspace(outputDataspace))
                           .c_str());
 
-    generateLuminanceScalesForOOTF(inputDataspace, outputDataspace, shader);
-    generateLuminanceNormalizationForOOTF(outputDataspace, shader);
+    generateLuminanceScalesForOOTF(inputDataspace, shader);
+    generateLuminanceNormalizationForOOTF(inputDataspace, outputDataspace, shader);
 
+    // Some tonemappers operate on CIE luminance, other tonemappers operate on linear rgb
+    // luminance in the source gamut.
     shader.append(R"(
-            float3 OOTF(float3 linearRGB, float3 xyz) {
+            float3 OOTF(float3 linearRGB) {
                 float3 scaledLinearRGB = ScaleLuminance(linearRGB);
-                float3 scaledXYZ = ScaleLuminance(xyz);
+                float3 scaledXYZ = ToXYZ(scaledLinearRGB);
 
-                float gain = libtonemap_LookupTonemapGain(scaledLinearRGB, scaledXYZ);
+                float gain = libtonemap_LookupTonemapGain(ToSrcRGB(scaledXYZ), scaledXYZ);
 
                 return NormalizeLuminance(scaledXYZ * gain);
             }
         )");
 }
 
-void generateOETF(ui::Dataspace dataspace, std::string& shader) {
-    switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) {
-        case HAL_DATASPACE_TRANSFER_ST2084:
-            shader.append(R"(
-
-                float3 OETF(float3 xyz) {
-                    float m1 = (2610.0 / 4096.0) / 4.0;
-                    float m2 = (2523.0 / 4096.0) * 128.0;
-                    float c1 = (3424.0 / 4096.0);
-                    float c2 = (2413.0 / 4096.0) * 32.0;
-                    float c3 = (2392.0 / 4096.0) * 32.0;
-
-                    float3 tmp = pow(xyz, float3(m1));
-                    tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
-                    return pow(tmp, float3(m2));
-                }
-            )");
-            break;
-        case HAL_DATASPACE_TRANSFER_HLG:
-            shader.append(R"(
-                float OETF_channel(float channel) {
-                    const float a = 0.17883277;
-                    const float b = 0.28466892;
-                    const float c = 0.55991073;
-                    return channel <= 1.0 / 12.0 ? sqrt(3.0 * channel) :
-                            a * log(12.0 * channel - b) + c;
-                }
-
-                float3 OETF(float3 linear) {
-                    return float3(OETF_channel(linear.r), OETF_channel(linear.g),
-                            OETF_channel(linear.b));
-                }
-            )");
-            break;
-        case HAL_DATASPACE_TRANSFER_LINEAR:
-            shader.append(R"(
-                float3 OETF(float3 linear) {
-                    return linear;
-                }
-            )");
-            break;
-        case HAL_DATASPACE_TRANSFER_SMPTE_170M:
-            shader.append(R"(
-                float OETF_sRGB(float linear) {
-                    return linear <= 0.018 ?
-                            linear * 4.50 : (pow(linear, 0.45) * 1.099) - 0.099;
-                }
-
-                float3 OETF_sRGB(float3 linear) {
-                    return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b));
-                }
-
-                float3 OETF(float3 linear) {
-                    return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb));
-                }
-            )");
-            break;
-        case HAL_DATASPACE_TRANSFER_GAMMA2_2:
-            shader.append(R"(
-                float OETF_sRGB(float linear) {
-                    return pow(linear, (1.0 / 2.2));
-                }
-
-                float3 OETF_sRGB(float3 linear) {
-                    return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b));
-                }
-
-                float3 OETF(float3 linear) {
-                    return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb));
-                }
-            )");
-            break;
-        case HAL_DATASPACE_TRANSFER_GAMMA2_6:
-            shader.append(R"(
-                float OETF_sRGB(float linear) {
-                    return pow(linear, (1.0 / 2.6));
-                }
-
-                float3 OETF_sRGB(float3 linear) {
-                    return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b));
-                }
-
-                float3 OETF(float3 linear) {
-                    return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb));
-                }
-            )");
-            break;
-        case HAL_DATASPACE_TRANSFER_GAMMA2_8:
-            shader.append(R"(
-                float OETF_sRGB(float linear) {
-                    return pow(linear, (1.0 / 2.8));
-                }
-
-                float3 OETF_sRGB(float3 linear) {
-                    return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b));
-                }
-
-                float3 OETF(float3 linear) {
-                    return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb));
-                }
-            )");
-            break;
-        case HAL_DATASPACE_TRANSFER_SRGB:
-        default:
-            shader.append(R"(
-                float OETF_sRGB(float linear) {
-                    return linear <= 0.0031308 ?
-                            linear * 12.92 : (pow(linear, 1.0 / 2.4) * 1.055) - 0.055;
-                }
-
-                float3 OETF_sRGB(float3 linear) {
-                    return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b));
-                }
-
-                float3 OETF(float3 linear) {
-                    return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb));
-                }
-            )");
-            break;
-    }
+void generateOETF(std::string& shader) {
+    // Only support gamma 2.2 for now
+    shader.append(R"(
+        float OETF(float3 linear) {
+            return sign(linear) * pow(abs(linear), (1.0 / 2.2));
+        }
+    )");
 }
 
 void generateEffectiveOOTF(bool undoPremultipliedAlpha, LinearEffect::SkSLType type,
-                           std::string& shader) {
+                           bool needsCustomOETF, std::string& shader) {
     switch (type) {
         case LinearEffect::SkSLType::ColorFilter:
             shader.append(R"(
@@ -408,11 +196,19 @@
             c.rgb = c.rgb / (c.a + 0.0019);
         )");
     }
+    // We are using linear sRGB as a working space, with 1.0 == 203 nits
     shader.append(R"(
-        float3 linearRGB = EOTF(c.rgb);
-        float3 xyz = ToXYZ(linearRGB);
-        c.rgb = OETF(ToRGB(OOTF(linearRGB, xyz)));
+        c.rgb = ApplyColorTransform(OOTF(toLinearSrgb(c.rgb)));
     )");
+    if (needsCustomOETF) {
+        shader.append(R"(
+            c.rgb = OETF(c.rgb);
+        )");
+    } else {
+        shader.append(R"(
+            c.rgb = fromLinearSrgb(c.rgb);
+        )");
+    }
     if (undoPremultipliedAlpha) {
         shader.append(R"(
             c.rgb = c.rgb * (c.a + 0.0019);
@@ -424,33 +220,6 @@
     )");
 }
 
-// please keep in sync with toSkColorSpace function in renderengine/skia/ColorSpaces.cpp
-ColorSpace toColorSpace(ui::Dataspace dataspace) {
-    switch (dataspace & HAL_DATASPACE_STANDARD_MASK) {
-        case HAL_DATASPACE_STANDARD_BT709:
-            return ColorSpace::sRGB();
-        case HAL_DATASPACE_STANDARD_DCI_P3:
-            return ColorSpace::DisplayP3();
-        case HAL_DATASPACE_STANDARD_BT2020:
-        case HAL_DATASPACE_STANDARD_BT2020_CONSTANT_LUMINANCE:
-            return ColorSpace::BT2020();
-        case HAL_DATASPACE_STANDARD_ADOBE_RGB:
-            return ColorSpace::AdobeRGB();
-        // TODO(b/208290320): BT601 format and variants return different primaries
-        case HAL_DATASPACE_STANDARD_BT601_625:
-        case HAL_DATASPACE_STANDARD_BT601_625_UNADJUSTED:
-        case HAL_DATASPACE_STANDARD_BT601_525:
-        case HAL_DATASPACE_STANDARD_BT601_525_UNADJUSTED:
-        // TODO(b/208290329): BT407M format returns different primaries
-        case HAL_DATASPACE_STANDARD_BT470M:
-        // TODO(b/208290904): FILM format returns different primaries
-        case HAL_DATASPACE_STANDARD_FILM:
-        case HAL_DATASPACE_STANDARD_UNSPECIFIED:
-        default:
-            return ColorSpace::sRGB();
-    }
-}
-
 template <typename T, std::enable_if_t<std::is_trivially_copyable<T>::value, bool> = true>
 std::vector<uint8_t> buildUniformValue(T value) {
     std::vector<uint8_t> result;
@@ -463,17 +232,45 @@
 
 std::string buildLinearEffectSkSL(const LinearEffect& linearEffect) {
     std::string shaderString;
-    generateEOTF(linearEffect.fakeInputDataspace == ui::Dataspace::UNKNOWN
-                         ? linearEffect.inputDataspace
-                         : linearEffect.fakeInputDataspace,
-                 shaderString);
     generateXYZTransforms(shaderString);
     generateOOTF(linearEffect.inputDataspace, linearEffect.outputDataspace, shaderString);
-    generateOETF(linearEffect.outputDataspace, shaderString);
-    generateEffectiveOOTF(linearEffect.undoPremultipliedAlpha, linearEffect.type, shaderString);
+
+    const bool needsCustomOETF = (linearEffect.fakeOutputDataspace & HAL_DATASPACE_TRANSFER_MASK) ==
+            HAL_DATASPACE_TRANSFER_GAMMA2_2;
+    if (needsCustomOETF) {
+        generateOETF(shaderString);
+    }
+    generateEffectiveOOTF(linearEffect.undoPremultipliedAlpha, linearEffect.type, needsCustomOETF,
+                          shaderString);
     return shaderString;
 }
 
+ColorSpace toColorSpace(ui::Dataspace dataspace) {
+    switch (dataspace & HAL_DATASPACE_STANDARD_MASK) {
+        case HAL_DATASPACE_STANDARD_BT709:
+            return ColorSpace::sRGB();
+        case HAL_DATASPACE_STANDARD_DCI_P3:
+            return ColorSpace::DisplayP3();
+        case HAL_DATASPACE_STANDARD_BT2020:
+        case HAL_DATASPACE_STANDARD_BT2020_CONSTANT_LUMINANCE:
+            return ColorSpace::BT2020();
+        case HAL_DATASPACE_STANDARD_ADOBE_RGB:
+            return ColorSpace::AdobeRGB();
+            // TODO(b/208290320): BT601 format and variants return different primaries
+        case HAL_DATASPACE_STANDARD_BT601_625:
+        case HAL_DATASPACE_STANDARD_BT601_625_UNADJUSTED:
+        case HAL_DATASPACE_STANDARD_BT601_525:
+        case HAL_DATASPACE_STANDARD_BT601_525_UNADJUSTED:
+            // TODO(b/208290329): BT407M format returns different primaries
+        case HAL_DATASPACE_STANDARD_BT470M:
+            // TODO(b/208290904): FILM format returns different primaries
+        case HAL_DATASPACE_STANDARD_FILM:
+        case HAL_DATASPACE_STANDARD_UNSPECIFIED:
+        default:
+            return ColorSpace::sRGB();
+    }
+}
+
 // Generates a list of uniforms to set on the LinearEffect shader above.
 std::vector<tonemap::ShaderUniform> buildLinearEffectUniforms(
         const LinearEffect& linearEffect, const mat4& colorTransform, float maxDisplayLuminance,
@@ -481,29 +278,29 @@
         aidl::android::hardware::graphics::composer3::RenderIntent renderIntent) {
     std::vector<tonemap::ShaderUniform> uniforms;
 
-    const ui::Dataspace inputDataspace = linearEffect.fakeInputDataspace == ui::Dataspace::UNKNOWN
-            ? linearEffect.inputDataspace
-            : linearEffect.fakeInputDataspace;
+    auto inputColorSpace = toColorSpace(linearEffect.inputDataspace);
+    auto outputColorSpace = toColorSpace(linearEffect.outputDataspace);
 
-    if (inputDataspace == linearEffect.outputDataspace) {
-        uniforms.push_back({.name = "in_rgbToXyz", .value = buildUniformValue<mat4>(mat4())});
-        uniforms.push_back(
-                {.name = "in_xyzToRgb", .value = buildUniformValue<mat4>(colorTransform)});
-    } else {
-        ColorSpace inputColorSpace = toColorSpace(inputDataspace);
-        ColorSpace outputColorSpace = toColorSpace(linearEffect.outputDataspace);
-        uniforms.push_back({.name = "in_rgbToXyz",
-                            .value = buildUniformValue<mat4>(mat4(inputColorSpace.getRGBtoXYZ()))});
-        uniforms.push_back({.name = "in_xyzToRgb",
-                            .value = buildUniformValue<mat4>(
-                                    colorTransform * mat4(outputColorSpace.getXYZtoRGB()))});
-    }
+    uniforms.push_back(
+            {.name = "in_rgbToXyz",
+             .value = buildUniformValue<mat3>(ColorSpace::linearExtendedSRGB().getRGBtoXYZ())});
+    uniforms.push_back({.name = "in_xyzToSrcRgb",
+                        .value = buildUniformValue<mat3>(inputColorSpace.getXYZtoRGB())});
+    // Transforms xyz colors to linear source colors, then applies the color transform, then
+    // transforms to linear extended RGB for skia to color manage.
+    uniforms.push_back({.name = "in_colorTransform",
+                        .value = buildUniformValue<mat4>(
+                                mat4(ColorSpace::linearExtendedSRGB().getXYZtoRGB()) *
+                                // TODO: the color transform ideally should be applied
+                                // in the source colorspace, but doing that breaks
+                                // renderengine tests
+                                mat4(outputColorSpace.getRGBtoXYZ()) * colorTransform *
+                                mat4(outputColorSpace.getXYZtoRGB()))});
 
     tonemap::Metadata metadata{.displayMaxLuminance = maxDisplayLuminance,
                                // If the input luminance is unknown, use display luminance (aka,
-                               // no-op any luminance changes)
-                               // This will be the case for eg screenshots in addition to
-                               // uncalibrated displays
+                               // no-op any luminance changes).
+                               // This is expected to only be meaningful for PQ content
                                .contentMaxLuminance =
                                        maxLuminance > 0 ? maxLuminance : maxDisplayLuminance,
                                .currentDisplayLuminance = currentDisplayLuminanceNits > 0
diff --git a/libs/shaders/tests/shaders_test.cpp b/libs/shaders/tests/shaders_test.cpp
index d45fb24..ba8bed2 100644
--- a/libs/shaders/tests/shaders_test.cpp
+++ b/libs/shaders/tests/shaders_test.cpp
@@ -35,6 +35,10 @@
     return arg.name == name && arg.value == value;
 }
 
+MATCHER_P(UniformNameEq, name, "") {
+    return arg.name == name;
+}
+
 template <typename T, std::enable_if_t<std::is_trivially_copyable<T>::value, bool> = true>
 std::vector<uint8_t> buildUniformValue(T value) {
     std::vector<uint8_t> result;
@@ -49,50 +53,44 @@
     shaders::LinearEffect effect =
             shaders::LinearEffect{.inputDataspace = ui::Dataspace::V0_SRGB_LINEAR,
                                   .outputDataspace = ui::Dataspace::V0_SRGB_LINEAR,
-                                  .fakeInputDataspace = ui::Dataspace::UNKNOWN};
+                                  .fakeOutputDataspace = ui::Dataspace::UNKNOWN};
 
     mat4 colorTransform = mat4::scale(vec4(.9, .9, .9, 1.));
     auto uniforms =
             shaders::buildLinearEffectUniforms(effect, colorTransform, 1.f, 1.f, 1.f, nullptr,
                                                aidl::android::hardware::graphics::composer3::
                                                        RenderIntent::COLORIMETRIC);
-    EXPECT_THAT(uniforms, Contains(UniformEq("in_rgbToXyz", buildUniformValue<mat4>(mat4()))));
     EXPECT_THAT(uniforms,
-                Contains(UniformEq("in_xyzToRgb", buildUniformValue<mat4>(colorTransform))));
+                Contains(UniformEq("in_rgbToXyz",
+                                   buildUniformValue<mat3>(
+                                           ColorSpace::linearExtendedSRGB().getRGBtoXYZ()))));
+    EXPECT_THAT(uniforms,
+                Contains(UniformEq("in_xyzToSrcRgb",
+                                   buildUniformValue<mat3>(
+                                           ColorSpace::linearSRGB().getXYZtoRGB()))));
+    // color transforms are already tested in renderengine's tests
+    EXPECT_THAT(uniforms, Contains(UniformNameEq("in_colorTransform")));
 }
 
 TEST_F(ShadersTest, buildLinearEffectUniforms_selectsGamutTransformMatrices) {
     shaders::LinearEffect effect =
             shaders::LinearEffect{.inputDataspace = ui::Dataspace::V0_SRGB,
                                   .outputDataspace = ui::Dataspace::DISPLAY_P3,
-                                  .fakeInputDataspace = ui::Dataspace::UNKNOWN};
+                                  .fakeOutputDataspace = ui::Dataspace::UNKNOWN};
 
     ColorSpace inputColorSpace = ColorSpace::sRGB();
-    ColorSpace outputColorSpace = ColorSpace::DisplayP3();
     auto uniforms =
             shaders::buildLinearEffectUniforms(effect, mat4(), 1.f, 1.f, 1.f, nullptr,
                                                aidl::android::hardware::graphics::composer3::
                                                        RenderIntent::COLORIMETRIC);
     EXPECT_THAT(uniforms,
                 Contains(UniformEq("in_rgbToXyz",
-                                   buildUniformValue<mat4>(mat4(inputColorSpace.getRGBtoXYZ())))));
+                                   buildUniformValue<mat3>(
+                                           ColorSpace::linearExtendedSRGB().getRGBtoXYZ()))));
     EXPECT_THAT(uniforms,
-                Contains(UniformEq("in_xyzToRgb",
-                                   buildUniformValue<mat4>(mat4(outputColorSpace.getXYZtoRGB())))));
-}
-
-TEST_F(ShadersTest, buildLinearEffectUniforms_respectsFakeInputDataspace) {
-    shaders::LinearEffect effect =
-            shaders::LinearEffect{.inputDataspace = ui::Dataspace::V0_SRGB,
-                                  .outputDataspace = ui::Dataspace::DISPLAY_P3,
-                                  .fakeInputDataspace = ui::Dataspace::DISPLAY_P3};
-
-    auto uniforms =
-            shaders::buildLinearEffectUniforms(effect, mat4(), 1.f, 1.f, 1.f, nullptr,
-                                               aidl::android::hardware::graphics::composer3::
-                                                       RenderIntent::COLORIMETRIC);
-    EXPECT_THAT(uniforms, Contains(UniformEq("in_rgbToXyz", buildUniformValue<mat4>(mat4()))));
-    EXPECT_THAT(uniforms, Contains(UniformEq("in_xyzToRgb", buildUniformValue<mat4>(mat4()))));
+                Contains(UniformEq("in_xyzToSrcRgb",
+                                   buildUniformValue<mat3>(inputColorSpace.getXYZtoRGB()))));
+    EXPECT_THAT(uniforms, Contains(UniformNameEq("in_colorTransform")));
 }
 
 } // namespace android
diff --git a/libs/ui/Gralloc4.cpp b/libs/ui/Gralloc4.cpp
index c3af996..b6274ab 100644
--- a/libs/ui/Gralloc4.cpp
+++ b/libs/ui/Gralloc4.cpp
@@ -766,162 +766,6 @@
                gralloc4::encodeSmpte2094_10);
 }
 
-template <class T>
-status_t Gralloc4Mapper::getDefault(uint32_t width, uint32_t height, PixelFormat format,
-                                    uint32_t layerCount, uint64_t usage,
-                                    const MetadataType& metadataType,
-                                    DecodeFunction<T> decodeFunction, T* outMetadata) const {
-    if (!outMetadata) {
-        return BAD_VALUE;
-    }
-
-    IMapper::BufferDescriptorInfo descriptorInfo;
-    if (auto error = sBufferDescriptorInfo("getDefault", width, height, format, layerCount, usage,
-                                           &descriptorInfo) != OK) {
-        return error;
-    }
-
-    hidl_vec<uint8_t> vec;
-    Error error;
-    auto ret = mMapper->getFromBufferDescriptorInfo(descriptorInfo, metadataType,
-                                                    [&](const auto& tmpError,
-                                                        const hidl_vec<uint8_t>& tmpVec) {
-                                                        error = tmpError;
-                                                        vec = tmpVec;
-                                                    });
-
-    if (!ret.isOk()) {
-        error = kTransactionError;
-    }
-
-    if (error != Error::NONE) {
-        ALOGE("getDefault(%s, %" PRIu64 ", ...) failed with %d", metadataType.name.c_str(),
-              metadataType.value, error);
-        return static_cast<status_t>(error);
-    }
-
-    return decodeFunction(vec, outMetadata);
-}
-
-status_t Gralloc4Mapper::getDefaultPixelFormatFourCC(uint32_t width, uint32_t height,
-                                                     PixelFormat format, uint32_t layerCount,
-                                                     uint64_t usage,
-                                                     uint32_t* outPixelFormatFourCC) const {
-    return getDefault(width, height, format, layerCount, usage,
-                      gralloc4::MetadataType_PixelFormatFourCC, gralloc4::decodePixelFormatFourCC,
-                      outPixelFormatFourCC);
-}
-
-status_t Gralloc4Mapper::getDefaultPixelFormatModifier(uint32_t width, uint32_t height,
-                                                       PixelFormat format, uint32_t layerCount,
-                                                       uint64_t usage,
-                                                       uint64_t* outPixelFormatModifier) const {
-    return getDefault(width, height, format, layerCount, usage,
-                      gralloc4::MetadataType_PixelFormatModifier,
-                      gralloc4::decodePixelFormatModifier, outPixelFormatModifier);
-}
-
-status_t Gralloc4Mapper::getDefaultAllocationSize(uint32_t width, uint32_t height,
-                                                  PixelFormat format, uint32_t layerCount,
-                                                  uint64_t usage,
-                                                  uint64_t* outAllocationSize) const {
-    return getDefault(width, height, format, layerCount, usage,
-                      gralloc4::MetadataType_AllocationSize, gralloc4::decodeAllocationSize,
-                      outAllocationSize);
-}
-
-status_t Gralloc4Mapper::getDefaultProtectedContent(uint32_t width, uint32_t height,
-                                                    PixelFormat format, uint32_t layerCount,
-                                                    uint64_t usage,
-                                                    uint64_t* outProtectedContent) const {
-    return getDefault(width, height, format, layerCount, usage,
-                      gralloc4::MetadataType_ProtectedContent, gralloc4::decodeProtectedContent,
-                      outProtectedContent);
-}
-
-status_t Gralloc4Mapper::getDefaultCompression(uint32_t width, uint32_t height, PixelFormat format,
-                                               uint32_t layerCount, uint64_t usage,
-                                               ExtendableType* outCompression) const {
-    return getDefault(width, height, format, layerCount, usage, gralloc4::MetadataType_Compression,
-                      gralloc4::decodeCompression, outCompression);
-}
-
-status_t Gralloc4Mapper::getDefaultCompression(uint32_t width, uint32_t height, PixelFormat format,
-                                               uint32_t layerCount, uint64_t usage,
-                                               ui::Compression* outCompression) const {
-    if (!outCompression) {
-        return BAD_VALUE;
-    }
-    ExtendableType compression;
-    status_t error = getDefaultCompression(width, height, format, layerCount, usage, &compression);
-    if (error) {
-        return error;
-    }
-    if (!gralloc4::isStandardCompression(compression)) {
-        return BAD_TYPE;
-    }
-    *outCompression = gralloc4::getStandardCompressionValue(compression);
-    return NO_ERROR;
-}
-
-status_t Gralloc4Mapper::getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format,
-                                              uint32_t layerCount, uint64_t usage,
-                                              ExtendableType* outInterlaced) const {
-    return getDefault(width, height, format, layerCount, usage, gralloc4::MetadataType_Interlaced,
-                      gralloc4::decodeInterlaced, outInterlaced);
-}
-
-status_t Gralloc4Mapper::getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format,
-                                              uint32_t layerCount, uint64_t usage,
-                                              ui::Interlaced* outInterlaced) const {
-    if (!outInterlaced) {
-        return BAD_VALUE;
-    }
-    ExtendableType interlaced;
-    status_t error = getDefaultInterlaced(width, height, format, layerCount, usage, &interlaced);
-    if (error) {
-        return error;
-    }
-    if (!gralloc4::isStandardInterlaced(interlaced)) {
-        return BAD_TYPE;
-    }
-    *outInterlaced = gralloc4::getStandardInterlacedValue(interlaced);
-    return NO_ERROR;
-}
-
-status_t Gralloc4Mapper::getDefaultChromaSiting(uint32_t width, uint32_t height, PixelFormat format,
-                                                uint32_t layerCount, uint64_t usage,
-                                                ExtendableType* outChromaSiting) const {
-    return getDefault(width, height, format, layerCount, usage, gralloc4::MetadataType_ChromaSiting,
-                      gralloc4::decodeChromaSiting, outChromaSiting);
-}
-
-status_t Gralloc4Mapper::getDefaultChromaSiting(uint32_t width, uint32_t height, PixelFormat format,
-                                                uint32_t layerCount, uint64_t usage,
-                                                ui::ChromaSiting* outChromaSiting) const {
-    if (!outChromaSiting) {
-        return BAD_VALUE;
-    }
-    ExtendableType chromaSiting;
-    status_t error =
-            getDefaultChromaSiting(width, height, format, layerCount, usage, &chromaSiting);
-    if (error) {
-        return error;
-    }
-    if (!gralloc4::isStandardChromaSiting(chromaSiting)) {
-        return BAD_TYPE;
-    }
-    *outChromaSiting = gralloc4::getStandardChromaSitingValue(chromaSiting);
-    return NO_ERROR;
-}
-
-status_t Gralloc4Mapper::getDefaultPlaneLayouts(
-        uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage,
-        std::vector<ui::PlaneLayout>* outPlaneLayouts) const {
-    return getDefault(width, height, format, layerCount, usage, gralloc4::MetadataType_PlaneLayouts,
-                      gralloc4::decodePlaneLayouts, outPlaneLayouts);
-}
-
 std::vector<MetadataTypeDescription> Gralloc4Mapper::listSupportedMetadataTypes() const {
     hidl_vec<MetadataTypeDescription> descriptions;
     Error error;
diff --git a/libs/ui/Gralloc5.cpp b/libs/ui/Gralloc5.cpp
index 6f196b8..2106839 100644
--- a/libs/ui/Gralloc5.cpp
+++ b/libs/ui/Gralloc5.cpp
@@ -352,14 +352,12 @@
         }
     }
     {
-        (void)stride;
-        // TODO(b/261856851): Add StandardMetadataType::STRIDE && enable this
-        //        auto value = getStandardMetadata<StandardMetadataType::STRIDE>(mMapper,
-        //        bufferHandle); if (static_cast<BufferUsage>(usage) != value) {
-        //            ALOGW("Layer count didn't match, expected %" PRIu64 " got %" PRId64, usage,
-        //                  static_cast<int64_t>(value.value_or(BufferUsage::CPU_READ_NEVER)));
-        //            return BAD_VALUE;
-        //        }
+        auto value = getStandardMetadata<StandardMetadataType::STRIDE>(mMapper, bufferHandle);
+        if (stride != value) {
+            ALOGW("Stride didn't match, expected %" PRIu32 " got %" PRId32, stride,
+                  value.value_or(-1));
+            return BAD_VALUE;
+        }
     }
     return OK;
 }
@@ -831,73 +829,4 @@
                                                                    smpte2094_10);
 }
 
-status_t Gralloc5Mapper::getDefaultPixelFormatFourCC(uint32_t, uint32_t, PixelFormat, uint32_t,
-                                                     uint64_t, uint32_t *) const {
-    // TODO(b/261857910): Remove
-    return UNKNOWN_TRANSACTION;
-}
-
-status_t Gralloc5Mapper::getDefaultPixelFormatModifier(uint32_t, uint32_t, PixelFormat, uint32_t,
-                                                       uint64_t, uint64_t *) const {
-    // TODO(b/261857910): Remove
-    return UNKNOWN_TRANSACTION;
-}
-
-status_t Gralloc5Mapper::getDefaultAllocationSize(uint32_t, uint32_t, PixelFormat, uint32_t,
-                                                  uint64_t, uint64_t *) const {
-    // TODO(b/261857910): Remove
-    return UNKNOWN_TRANSACTION;
-}
-
-status_t Gralloc5Mapper::getDefaultProtectedContent(uint32_t, uint32_t, PixelFormat, uint32_t,
-                                                    uint64_t, uint64_t *) const {
-    // TODO(b/261857910): Remove
-    return UNKNOWN_TRANSACTION;
-}
-
-status_t Gralloc5Mapper::getDefaultCompression(
-        uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t,
-        aidl::android::hardware::graphics::common::ExtendableType *) const {
-    // TODO(b/261857910): Remove
-    return UNKNOWN_TRANSACTION;
-}
-
-status_t Gralloc5Mapper::getDefaultCompression(uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t,
-                                               ui::Compression *) const {
-    // TODO(b/261857910): Remove
-    return UNKNOWN_TRANSACTION;
-}
-
-status_t Gralloc5Mapper::getDefaultInterlaced(
-        uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t,
-        aidl::android::hardware::graphics::common::ExtendableType *) const {
-    // TODO(b/261857910): Remove
-    return UNKNOWN_TRANSACTION;
-}
-
-status_t Gralloc5Mapper::getDefaultInterlaced(uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t,
-                                              ui::Interlaced *) const {
-    // TODO(b/261857910): Remove
-    return UNKNOWN_TRANSACTION;
-}
-
-status_t Gralloc5Mapper::getDefaultChromaSiting(
-        uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t,
-        aidl::android::hardware::graphics::common::ExtendableType *) const {
-    // TODO(b/261857910): Remove
-    return UNKNOWN_TRANSACTION;
-}
-
-status_t Gralloc5Mapper::getDefaultChromaSiting(uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t,
-                                                ui::ChromaSiting *) const {
-    // TODO(b/261857910): Remove
-    return UNKNOWN_TRANSACTION;
-}
-
-status_t Gralloc5Mapper::getDefaultPlaneLayouts(uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t,
-                                                std::vector<ui::PlaneLayout> *) const {
-    // TODO(b/261857910): Remove
-    return UNKNOWN_TRANSACTION;
-}
-
 } // namespace android
\ No newline at end of file
diff --git a/libs/ui/GraphicBufferMapper.cpp b/libs/ui/GraphicBufferMapper.cpp
index 6002a6d..7086e04 100644
--- a/libs/ui/GraphicBufferMapper.cpp
+++ b/libs/ui/GraphicBufferMapper.cpp
@@ -341,84 +341,5 @@
     return mMapper->setSmpte2094_10(bufferHandle, smpte2094_10);
 }
 
-status_t GraphicBufferMapper::getDefaultPixelFormatFourCC(uint32_t width, uint32_t height,
-                                                          PixelFormat format, uint32_t layerCount,
-                                                          uint64_t usage,
-                                                          uint32_t* outPixelFormatFourCC) {
-    return mMapper->getDefaultPixelFormatFourCC(width, height, format, layerCount, usage,
-                                                outPixelFormatFourCC);
-}
-
-status_t GraphicBufferMapper::getDefaultPixelFormatModifier(uint32_t width, uint32_t height,
-                                                            PixelFormat format, uint32_t layerCount,
-                                                            uint64_t usage,
-                                                            uint64_t* outPixelFormatModifier) {
-    return mMapper->getDefaultPixelFormatModifier(width, height, format, layerCount, usage,
-                                                  outPixelFormatModifier);
-}
-
-status_t GraphicBufferMapper::getDefaultAllocationSize(uint32_t width, uint32_t height,
-                                                       PixelFormat format, uint32_t layerCount,
-                                                       uint64_t usage,
-                                                       uint64_t* outAllocationSize) {
-    return mMapper->getDefaultAllocationSize(width, height, format, layerCount, usage,
-                                             outAllocationSize);
-}
-
-status_t GraphicBufferMapper::getDefaultProtectedContent(uint32_t width, uint32_t height,
-                                                         PixelFormat format, uint32_t layerCount,
-                                                         uint64_t usage,
-                                                         uint64_t* outProtectedContent) {
-    return mMapper->getDefaultProtectedContent(width, height, format, layerCount, usage,
-                                               outProtectedContent);
-}
-
-status_t GraphicBufferMapper::getDefaultCompression(
-        uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage,
-        aidl::android::hardware::graphics::common::ExtendableType* outCompression) {
-    return mMapper->getDefaultCompression(width, height, format, layerCount, usage, outCompression);
-}
-
-status_t GraphicBufferMapper::getDefaultCompression(uint32_t width, uint32_t height,
-                                                    PixelFormat format, uint32_t layerCount,
-                                                    uint64_t usage,
-                                                    ui::Compression* outCompression) {
-    return mMapper->getDefaultCompression(width, height, format, layerCount, usage, outCompression);
-}
-
-status_t GraphicBufferMapper::getDefaultInterlaced(
-        uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage,
-        aidl::android::hardware::graphics::common::ExtendableType* outInterlaced) {
-    return mMapper->getDefaultInterlaced(width, height, format, layerCount, usage, outInterlaced);
-}
-
-status_t GraphicBufferMapper::getDefaultInterlaced(uint32_t width, uint32_t height,
-                                                   PixelFormat format, uint32_t layerCount,
-                                                   uint64_t usage, ui::Interlaced* outInterlaced) {
-    return mMapper->getDefaultInterlaced(width, height, format, layerCount, usage, outInterlaced);
-}
-
-status_t GraphicBufferMapper::getDefaultChromaSiting(
-        uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage,
-        aidl::android::hardware::graphics::common::ExtendableType* outChromaSiting) {
-    return mMapper->getDefaultChromaSiting(width, height, format, layerCount, usage,
-                                           outChromaSiting);
-}
-
-status_t GraphicBufferMapper::getDefaultChromaSiting(uint32_t width, uint32_t height,
-                                                     PixelFormat format, uint32_t layerCount,
-                                                     uint64_t usage,
-                                                     ui::ChromaSiting* outChromaSiting) {
-    return mMapper->getDefaultChromaSiting(width, height, format, layerCount, usage,
-                                           outChromaSiting);
-}
-
-status_t GraphicBufferMapper::getDefaultPlaneLayouts(
-        uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage,
-        std::vector<ui::PlaneLayout>* outPlaneLayouts) {
-    return mMapper->getDefaultPlaneLayouts(width, height, format, layerCount, usage,
-                                           outPlaneLayouts);
-}
-
 // ---------------------------------------------------------------------------
 }; // namespace android
diff --git a/libs/ui/include/ui/Gralloc.h b/libs/ui/include/ui/Gralloc.h
index b494cbe..496ba57 100644
--- a/libs/ui/include/ui/Gralloc.h
+++ b/libs/ui/include/ui/Gralloc.h
@@ -200,72 +200,6 @@
                                      std::optional<std::vector<uint8_t>> /*smpte2094_10*/) const {
         return INVALID_OPERATION;
     }
-    virtual status_t getDefaultPixelFormatFourCC(uint32_t /*width*/, uint32_t /*height*/,
-                                                 PixelFormat /*format*/, uint32_t /*layerCount*/,
-                                                 uint64_t /*usage*/,
-                                                 uint32_t* /*outPixelFormatFourCC*/) const {
-        return INVALID_OPERATION;
-    }
-    virtual status_t getDefaultPixelFormatModifier(uint32_t /*width*/, uint32_t /*height*/,
-                                                   PixelFormat /*format*/, uint32_t /*layerCount*/,
-                                                   uint64_t /*usage*/,
-                                                   uint64_t* /*outPixelFormatModifier*/) const {
-        return INVALID_OPERATION;
-    }
-    virtual status_t getDefaultAllocationSize(uint32_t /*width*/, uint32_t /*height*/,
-                                              PixelFormat /*format*/, uint32_t /*layerCount*/,
-                                              uint64_t /*usage*/,
-                                              uint64_t* /*outAllocationSize*/) const {
-        return INVALID_OPERATION;
-    }
-    virtual status_t getDefaultProtectedContent(uint32_t /*width*/, uint32_t /*height*/,
-                                                PixelFormat /*format*/, uint32_t /*layerCount*/,
-                                                uint64_t /*usage*/,
-                                                uint64_t* /*outProtectedContent*/) const {
-        return INVALID_OPERATION;
-    }
-    virtual status_t getDefaultCompression(
-            uint32_t /*width*/, uint32_t /*height*/, PixelFormat /*format*/,
-            uint32_t /*layerCount*/, uint64_t /*usage*/,
-            aidl::android::hardware::graphics::common::ExtendableType* /*outCompression*/) const {
-        return INVALID_OPERATION;
-    }
-    virtual status_t getDefaultCompression(uint32_t /*width*/, uint32_t /*height*/,
-                                           PixelFormat /*format*/, uint32_t /*layerCount*/,
-                                           uint64_t /*usage*/,
-                                           ui::Compression* /*outCompression*/) const {
-        return INVALID_OPERATION;
-    }
-    virtual status_t getDefaultInterlaced(
-            uint32_t /*width*/, uint32_t /*height*/, PixelFormat /*format*/,
-            uint32_t /*layerCount*/, uint64_t /*usage*/,
-            aidl::android::hardware::graphics::common::ExtendableType* /*outInterlaced*/) const {
-        return INVALID_OPERATION;
-    }
-    virtual status_t getDefaultInterlaced(uint32_t /*width*/, uint32_t /*height*/,
-                                          PixelFormat /*format*/, uint32_t /*layerCount*/,
-                                          uint64_t /*usage*/,
-                                          ui::Interlaced* /*outInterlaced*/) const {
-        return INVALID_OPERATION;
-    }
-    virtual status_t getDefaultChromaSiting(
-            uint32_t /*width*/, uint32_t /*height*/, PixelFormat /*format*/,
-            uint32_t /*layerCount*/, uint64_t /*usage*/,
-            aidl::android::hardware::graphics::common::ExtendableType* /*outChromaSiting*/) const {
-        return INVALID_OPERATION;
-    }
-    virtual status_t getDefaultChromaSiting(uint32_t /*width*/, uint32_t /*height*/,
-                                            PixelFormat /*format*/, uint32_t /*layerCount*/,
-                                            uint64_t /*usage*/,
-                                            ui::ChromaSiting* /*outChromaSiting*/) const {
-        return INVALID_OPERATION;
-    }
-    virtual status_t getDefaultPlaneLayouts(
-            uint32_t /*width*/, uint32_t /*height*/, PixelFormat /*format*/,
-            uint32_t /*layerCount*/, uint64_t /*usage*/,
-            std::vector<ui::PlaneLayout>* /*outPlaneLayouts*/) const {
-        return INVALID_OPERATION;
-    }
 };
 
 // A wrapper to IAllocator
diff --git a/libs/ui/include/ui/Gralloc4.h b/libs/ui/include/ui/Gralloc4.h
index 6bc5ce5..df43be8 100644
--- a/libs/ui/include/ui/Gralloc4.h
+++ b/libs/ui/include/ui/Gralloc4.h
@@ -120,42 +120,6 @@
                              std::optional<std::vector<uint8_t>>* outSmpte2094_10) const override;
     status_t setSmpte2094_10(buffer_handle_t bufferHandle,
                              std::optional<std::vector<uint8_t>> smpte2094_10) const override;
-    status_t getDefaultPixelFormatFourCC(uint32_t width, uint32_t height, PixelFormat format,
-                                         uint32_t layerCount, uint64_t usage,
-                                         uint32_t* outPixelFormatFourCC) const override;
-    status_t getDefaultPixelFormatModifier(uint32_t width, uint32_t height, PixelFormat format,
-                                           uint32_t layerCount, uint64_t usage,
-                                           uint64_t* outPixelFormatModifier) const override;
-    status_t getDefaultAllocationSize(uint32_t width, uint32_t height, PixelFormat format,
-                                      uint32_t layerCount, uint64_t usage,
-                                      uint64_t* outAllocationSize) const override;
-    status_t getDefaultProtectedContent(uint32_t width, uint32_t height, PixelFormat format,
-                                        uint32_t layerCount, uint64_t usage,
-                                        uint64_t* outProtectedContent) const override;
-    status_t getDefaultCompression(uint32_t width, uint32_t height, PixelFormat format,
-                                   uint32_t layerCount, uint64_t usage,
-                                   aidl::android::hardware::graphics::common::ExtendableType*
-                                           outCompression) const override;
-    status_t getDefaultCompression(uint32_t width, uint32_t height, PixelFormat format,
-                                   uint32_t layerCount, uint64_t usage,
-                                   ui::Compression* outCompression) const override;
-    status_t getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format,
-                                  uint32_t layerCount, uint64_t usage,
-                                  aidl::android::hardware::graphics::common::ExtendableType*
-                                          outInterlaced) const override;
-    status_t getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format,
-                                  uint32_t layerCount, uint64_t usage,
-                                  ui::Interlaced* outInterlaced) const override;
-    status_t getDefaultChromaSiting(uint32_t width, uint32_t height, PixelFormat format,
-                                    uint32_t layerCount, uint64_t usage,
-                                    aidl::android::hardware::graphics::common::ExtendableType*
-                                            outChromaSiting) const override;
-    status_t getDefaultChromaSiting(uint32_t width, uint32_t height, PixelFormat format,
-                                    uint32_t layerCount, uint64_t usage,
-                                    ui::ChromaSiting* outChromaSiting) const override;
-    status_t getDefaultPlaneLayouts(uint32_t width, uint32_t height, PixelFormat format,
-                                    uint32_t layerCount, uint64_t usage,
-                                    std::vector<ui::PlaneLayout>* outPlaneLayouts) const override;
 
     std::vector<android::hardware::graphics::mapper::V4_0::IMapper::MetadataTypeDescription>
     listSupportedMetadataTypes() const;
diff --git a/libs/ui/include/ui/Gralloc5.h b/libs/ui/include/ui/Gralloc5.h
index bc10169..44b97d1 100644
--- a/libs/ui/include/ui/Gralloc5.h
+++ b/libs/ui/include/ui/Gralloc5.h
@@ -156,60 +156,6 @@
             buffer_handle_t bufferHandle,
             std::optional<std::vector<uint8_t>> smpte2094_10) const override;
 
-    [[nodiscard]] status_t getDefaultPixelFormatFourCC(
-            uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
-            uint64_t usage, uint32_t *outPixelFormatFourCC) const override;
-
-    [[nodiscard]] status_t getDefaultPixelFormatModifier(
-            uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
-            uint64_t usage, uint64_t *outPixelFormatModifier) const override;
-
-    [[nodiscard]] status_t getDefaultAllocationSize(uint32_t width, uint32_t height,
-                                                    PixelFormat format, uint32_t layerCount,
-                                                    uint64_t usage,
-                                                    uint64_t *outAllocationSize) const override;
-
-    [[nodiscard]] status_t getDefaultProtectedContent(uint32_t width, uint32_t height,
-                                                      PixelFormat format, uint32_t layerCount,
-                                                      uint64_t usage,
-                                                      uint64_t *outProtectedContent) const override;
-
-    [[nodiscard]] status_t getDefaultCompression(
-            uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
-            uint64_t usage,
-            aidl::android::hardware::graphics::common::ExtendableType *outCompression)
-            const override;
-
-    [[nodiscard]] status_t getDefaultCompression(uint32_t width, uint32_t height,
-                                                 PixelFormat format, uint32_t layerCount,
-                                                 uint64_t usage,
-                                                 ui::Compression *outCompression) const override;
-
-    [[nodiscard]] status_t getDefaultInterlaced(
-            uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
-            uint64_t usage,
-            aidl::android::hardware::graphics::common::ExtendableType *outInterlaced)
-            const override;
-
-    [[nodiscard]] status_t getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format,
-                                                uint32_t layerCount, uint64_t usage,
-                                                ui::Interlaced *outInterlaced) const override;
-
-    [[nodiscard]] status_t getDefaultChromaSiting(
-            uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
-            uint64_t usage,
-            aidl::android::hardware::graphics::common::ExtendableType *outChromaSiting)
-            const override;
-
-    [[nodiscard]] status_t getDefaultChromaSiting(uint32_t width, uint32_t height,
-                                                  PixelFormat format, uint32_t layerCount,
-                                                  uint64_t usage,
-                                                  ui::ChromaSiting *outChromaSiting) const override;
-
-    [[nodiscard]] status_t getDefaultPlaneLayouts(
-            uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
-            uint64_t usage, std::vector<ui::PlaneLayout> *outPlaneLayouts) const override;
-
 private:
     void unlockBlocking(buffer_handle_t bufferHandle) const;
 
diff --git a/libs/ui/include/ui/GraphicBufferMapper.h b/libs/ui/include/ui/GraphicBufferMapper.h
index 51c6e92..3a5167a 100644
--- a/libs/ui/include/ui/GraphicBufferMapper.h
+++ b/libs/ui/include/ui/GraphicBufferMapper.h
@@ -138,48 +138,6 @@
     status_t setSmpte2094_10(buffer_handle_t bufferHandle,
                              std::optional<std::vector<uint8_t>> smpte2094_10);
 
-    /**
-     * Gets the default metadata for a gralloc buffer allocated with the given parameters.
-     *
-     * These functions are supported by gralloc 4.0+.
-     */
-    status_t getDefaultPixelFormatFourCC(uint32_t width, uint32_t height, PixelFormat format,
-                                         uint32_t layerCount, uint64_t usage,
-                                         uint32_t* outPixelFormatFourCC);
-    status_t getDefaultPixelFormatModifier(uint32_t width, uint32_t height, PixelFormat format,
-                                           uint32_t layerCount, uint64_t usage,
-                                           uint64_t* outPixelFormatModifier);
-    status_t getDefaultAllocationSize(uint32_t width, uint32_t height, PixelFormat format,
-                                      uint32_t layerCount, uint64_t usage,
-                                      uint64_t* outAllocationSize);
-    status_t getDefaultProtectedContent(uint32_t width, uint32_t height, PixelFormat format,
-                                        uint32_t layerCount, uint64_t usage,
-                                        uint64_t* outProtectedContent);
-    status_t getDefaultCompression(
-            uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
-            uint64_t usage,
-            aidl::android::hardware::graphics::common::ExtendableType* outCompression);
-    status_t getDefaultCompression(uint32_t width, uint32_t height, PixelFormat format,
-                                   uint32_t layerCount, uint64_t usage,
-                                   ui::Compression* outCompression);
-    status_t getDefaultInterlaced(
-            uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
-            uint64_t usage,
-            aidl::android::hardware::graphics::common::ExtendableType* outInterlaced);
-    status_t getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format,
-                                  uint32_t layerCount, uint64_t usage,
-                                  ui::Interlaced* outInterlaced);
-    status_t getDefaultChromaSiting(
-            uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
-            uint64_t usage,
-            aidl::android::hardware::graphics::common::ExtendableType* outChromaSiting);
-    status_t getDefaultChromaSiting(uint32_t width, uint32_t height, PixelFormat format,
-                                    uint32_t layerCount, uint64_t usage,
-                                    ui::ChromaSiting* outChromaSiting);
-    status_t getDefaultPlaneLayouts(uint32_t width, uint32_t height, PixelFormat format,
-                                    uint32_t layerCount, uint64_t usage,
-                                    std::vector<ui::PlaneLayout>* outPlaneLayouts);
-
     const GrallocMapper& getGrallocMapper() const {
         return reinterpret_cast<const GrallocMapper&>(*mMapper);
     }
diff --git a/libs/jpegrecoverymap/Android.bp b/libs/ultrahdr/Android.bp
similarity index 89%
rename from libs/jpegrecoverymap/Android.bp
rename to libs/ultrahdr/Android.bp
index 0c03ede..e3f709b 100644
--- a/libs/jpegrecoverymap/Android.bp
+++ b/libs/ultrahdr/Android.bp
@@ -22,15 +22,16 @@
 }
 
 cc_library {
-    name: "libjpegrecoverymap",
+    name: "libultrahdr",
     host_supported: true,
-
+    vendor_available: true,
     export_include_dirs: ["include"],
     local_include_dirs: ["include"],
 
     srcs: [
+        "icc.cpp",
         "jpegr.cpp",
-        "recoverymapmath.cpp",
+        "gainmapmath.cpp",
         "jpegrutils.cpp",
         "multipictureformat.cpp",
     ],
@@ -43,17 +44,17 @@
         "liblog",
         "libutils",
     ],
-
-    static_libs: ["libskia"],
 }
 
 cc_library {
     name: "libjpegencoder",
     host_supported: true,
+    vendor_available: true,
 
     shared_libs: [
         "libjpeg",
         "liblog",
+        "libutils",
     ],
 
     export_include_dirs: ["include"],
@@ -66,10 +67,12 @@
 cc_library {
     name: "libjpegdecoder",
     host_supported: true,
+    vendor_available: true,
 
     shared_libs: [
         "libjpeg",
         "liblog",
+        "libutils",
     ],
 
     export_include_dirs: ["include"],
diff --git a/libs/jpegrecoverymap/OWNERS b/libs/ultrahdr/OWNERS
similarity index 100%
rename from libs/jpegrecoverymap/OWNERS
rename to libs/ultrahdr/OWNERS
diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/ultrahdr/gainmapmath.cpp
similarity index 87%
rename from libs/jpegrecoverymap/recoverymapmath.cpp
rename to libs/ultrahdr/gainmapmath.cpp
index 20c32ed..37c3cf3 100644
--- a/libs/jpegrecoverymap/recoverymapmath.cpp
+++ b/libs/ultrahdr/gainmapmath.cpp
@@ -16,9 +16,9 @@
 
 #include <cmath>
 #include <vector>
-#include <jpegrecoverymap/recoverymapmath.h>
+#include <ultrahdr/gainmapmath.h>
 
-namespace android::jpegrecoverymap {
+namespace android::ultrahdr {
 
 static const std::vector<float> kPqOETF = [] {
     std::vector<float> result;
@@ -396,53 +396,60 @@
 
 // TODO: confirm we always want to convert like this before calculating
 // luminance.
-ColorTransformFn getHdrConversionFn(jpegr_color_gamut sdr_gamut, jpegr_color_gamut hdr_gamut) {
+ColorTransformFn getHdrConversionFn(ultrahdr_color_gamut sdr_gamut,
+                                    ultrahdr_color_gamut hdr_gamut) {
   switch (sdr_gamut) {
-    case JPEGR_COLORGAMUT_BT709:
+    case ULTRAHDR_COLORGAMUT_BT709:
       switch (hdr_gamut) {
-        case JPEGR_COLORGAMUT_BT709:
+        case ULTRAHDR_COLORGAMUT_BT709:
           return identityConversion;
-        case JPEGR_COLORGAMUT_P3:
+        case ULTRAHDR_COLORGAMUT_P3:
           return p3ToBt709;
-        case JPEGR_COLORGAMUT_BT2100:
+        case ULTRAHDR_COLORGAMUT_BT2100:
           return bt2100ToBt709;
-        case JPEGR_COLORGAMUT_UNSPECIFIED:
+        case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
           return nullptr;
       }
       break;
-    case JPEGR_COLORGAMUT_P3:
+    case ULTRAHDR_COLORGAMUT_P3:
       switch (hdr_gamut) {
-        case JPEGR_COLORGAMUT_BT709:
+        case ULTRAHDR_COLORGAMUT_BT709:
           return bt709ToP3;
-        case JPEGR_COLORGAMUT_P3:
+        case ULTRAHDR_COLORGAMUT_P3:
           return identityConversion;
-        case JPEGR_COLORGAMUT_BT2100:
+        case ULTRAHDR_COLORGAMUT_BT2100:
           return bt2100ToP3;
-        case JPEGR_COLORGAMUT_UNSPECIFIED:
+        case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
           return nullptr;
       }
       break;
-    case JPEGR_COLORGAMUT_BT2100:
+    case ULTRAHDR_COLORGAMUT_BT2100:
       switch (hdr_gamut) {
-        case JPEGR_COLORGAMUT_BT709:
+        case ULTRAHDR_COLORGAMUT_BT709:
           return bt709ToBt2100;
-        case JPEGR_COLORGAMUT_P3:
+        case ULTRAHDR_COLORGAMUT_P3:
           return p3ToBt2100;
-        case JPEGR_COLORGAMUT_BT2100:
+        case ULTRAHDR_COLORGAMUT_BT2100:
           return identityConversion;
-        case JPEGR_COLORGAMUT_UNSPECIFIED:
+        case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
           return nullptr;
       }
       break;
-    case JPEGR_COLORGAMUT_UNSPECIFIED:
+    case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
       return nullptr;
   }
 }
 
 
 ////////////////////////////////////////////////////////////////////////////////
-// Recovery map calculations
-uint8_t encodeRecovery(float y_sdr, float y_hdr, jr_metadata_ptr metadata) {
+// Gain map calculations
+uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata) {
+  return encodeGain(y_sdr, y_hdr, metadata,
+                    log2(metadata->minContentBoost), log2(metadata->maxContentBoost));
+}
+
+uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata,
+                   float log2MinContentBoost, float log2MaxContentBoost) {
   float gain = 1.0f;
   if (y_sdr > 0.0f) {
     gain = y_hdr / y_sdr;
@@ -451,21 +458,28 @@
   if (gain < metadata->minContentBoost) gain = metadata->minContentBoost;
   if (gain > metadata->maxContentBoost) gain = metadata->maxContentBoost;
 
-  return static_cast<uint8_t>((log2(gain) - log2(metadata->minContentBoost))
-                            / (log2(metadata->maxContentBoost) - log2(metadata->minContentBoost))
+  return static_cast<uint8_t>((log2(gain) - log2MinContentBoost)
+                            / (log2MaxContentBoost - log2MinContentBoost)
                             * 255.0f);
 }
 
-Color applyRecovery(Color e, float recovery, jr_metadata_ptr metadata) {
-  float logBoost = log2(metadata->minContentBoost) * (1.0f - recovery)
-                 + log2(metadata->maxContentBoost) * recovery;
-  float recoveryFactor = exp2(logBoost);
-  return e * recoveryFactor;
+Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata) {
+  float logBoost = log2(metadata->minContentBoost) * (1.0f - gain)
+                 + log2(metadata->maxContentBoost) * gain;
+  float gainFactor = exp2(logBoost);
+  return e * gainFactor;
 }
 
-Color applyRecoveryLUT(Color e, float recovery, RecoveryLUT& recoveryLUT) {
-  float recoveryFactor = recoveryLUT.getRecoveryFactor(recovery);
-  return e * recoveryFactor;
+Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata, float displayBoost) {
+  float logBoost = log2(metadata->minContentBoost) * (1.0f - gain)
+                 + log2(metadata->maxContentBoost) * gain;
+  float gainFactor = exp2(logBoost * displayBoost / metadata->maxContentBoost);
+  return e * gainFactor;
+}
+
+Color applyGainLUT(Color e, float gain, GainLUT& gainLUT) {
+  float gainFactor = gainLUT.getGainFactor(gain);
+  return e * gainFactor;
 }
 
 Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
@@ -486,17 +500,28 @@
 }
 
 Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
-  size_t pixel_count = image->width * image->height;
+  size_t luma_stride = image->luma_stride;
+  size_t chroma_stride = image->chroma_stride;
+  uint16_t* luma_data = reinterpret_cast<uint16_t*>(image->data);
+  uint16_t* chroma_data = reinterpret_cast<uint16_t*>(image->chroma_data);
 
-  size_t pixel_y_idx = x + y * image->width;
-  size_t pixel_uv_idx = x / 2 + (y / 2) * (image->width / 2);
+  if (luma_stride == 0) {
+    luma_stride = image->width;
+  }
+  if (chroma_stride == 0) {
+    chroma_stride = luma_stride;
+  }
+  if (chroma_data == nullptr) {
+    chroma_data = &reinterpret_cast<uint16_t*>(image->data)[luma_stride * image->height];
+  }
 
-  uint16_t y_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_y_idx]
-                  >> 6;
-  uint16_t u_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_count + pixel_uv_idx * 2]
-                  >> 6;
-  uint16_t v_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_count + pixel_uv_idx * 2 + 1]
-                  >> 6;
+  size_t pixel_y_idx = y * luma_stride + x;
+  size_t pixel_u_idx = (y >> 1) * chroma_stride + (x & ~0x1);
+  size_t pixel_v_idx = pixel_u_idx + 1;
+
+  uint16_t y_uint = luma_data[pixel_y_idx] >> 6;
+  uint16_t u_uint = chroma_data[pixel_u_idx] >> 6;
+  uint16_t v_uint = chroma_data[pixel_v_idx] >> 6;
 
   // Conversions include taking narrow-range into account.
   return {{{ (static_cast<float>(y_uint) - 64.0f) / 876.0f,
@@ -638,4 +663,4 @@
        | (((uint64_t) floatToHalf(1.0f)) << 48);
 }
 
-} // namespace android::jpegrecoverymap
+} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/icc.cpp b/libs/ultrahdr/icc.cpp
new file mode 100644
index 0000000..c807705
--- /dev/null
+++ b/libs/ultrahdr/icc.cpp
@@ -0,0 +1,585 @@
+/*
+ * 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 <ultrahdr/icc.h>
+#include <ultrahdr/gainmapmath.h>
+#include <vector>
+#include <utils/Log.h>
+
+#ifndef FLT_MAX
+#define FLT_MAX 0x1.fffffep127f
+#endif
+
+namespace android::ultrahdr {
+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 ultrahdr_transfer_function tf,
+                                       const ultrahdr_color_gamut gamut) {
+    std::string result;
+    switch (gamut) {
+        case ULTRAHDR_COLORGAMUT_BT709:
+            result += "sRGB";
+            break;
+        case ULTRAHDR_COLORGAMUT_P3:
+            result += "Display P3";
+            break;
+        case ULTRAHDR_COLORGAMUT_BT2100:
+            result += "Rec2020";
+            break;
+        default:
+            result += "Unknown";
+            break;
+    }
+    result += " Gamut with ";
+    switch (tf) {
+        case ULTRAHDR_TF_SRGB:
+            result += "sRGB";
+            break;
+        case ULTRAHDR_TF_LINEAR:
+            result += "Linear";
+            break;
+        case ULTRAHDR_TF_PQ:
+            result += "PQ";
+            break;
+        case ULTRAHDR_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 ultrahdr_transfer_function tf, float L) {
+    if (L <= 0.f) {
+        return 1.f;
+    }
+    if (tf == ULTRAHDR_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 == ULTRAHDR_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(ULTRAHDR_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(ultrahdr_transfer_function tf,
+                                          ultrahdr_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 ULTRAHDR_COLORGAMUT_BT709:
+            toXYZD50 = kSRGB;
+            break;
+        case ULTRAHDR_COLORGAMUT_P3:
+            toXYZD50 = kDisplayP3;
+            break;
+        case ULTRAHDR_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 != ULTRAHDR_TF_PQ) {
+        if (tf == ULTRAHDR_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 == ULTRAHDR_TF_HLG || tf == ULTRAHDR_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 == ULTRAHDR_COLORGAMUT_BT709) {
+            color_primaries = kCICPPrimariesSRGB;
+        } else if (gamut == ULTRAHDR_COLORGAMUT_P3) {
+            color_primaries = kCICPPrimariesP3;
+        }
+
+        uint32_t transfer_characteristics = 0;
+        if (tf == ULTRAHDR_TF_SRGB) {
+            transfer_characteristics = kCICPTrfnSRGB;
+        } else if (tf == ULTRAHDR_TF_LINEAR) {
+            transfer_characteristics = kCICPTrfnLinear;
+        } else if (tf == ULTRAHDR_TF_PQ) {
+            transfer_characteristics = kCICPTrfnPQ;
+        } else if (tf == ULTRAHDR_TF_HLG) {
+            transfer_characteristics = kCICPTrfnHLG;
+        }
+        tags.emplace_back(kTAG_cicp, write_cicp_tag(color_primaries, transfer_characteristics));
+    }
+
+    // Compute A2B0.
+    if (tf == ULTRAHDR_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 == ULTRAHDR_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 == ULTRAHDR_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::ultrahdr
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/ultrahdr/include/ultrahdr/gainmapmath.h
similarity index 77%
rename from libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
rename to libs/ultrahdr/include/ultrahdr/gainmapmath.h
index 8b5318f..abc9356 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
+++ b/libs/ultrahdr/include/ultrahdr/gainmapmath.h
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H
-#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H
+#ifndef ANDROID_ULTRAHDR_RECOVERYMAPMATH_H
+#define ANDROID_ULTRAHDR_RECOVERYMAPMATH_H
 
 #include <cmath>
 #include <stdint.h>
 
-#include <jpegrecoverymap/jpegr.h>
+#include <ultrahdr/jpegr.h>
 
-namespace android::jpegrecoverymap {
+namespace android::ultrahdr {
 
 #define CLIP3(x, min, max) ((x) < (min)) ? (min) : ((x) > (max)) ? (max) : (x)
 
@@ -116,37 +116,53 @@
 }
 
 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;
+  // round-to-nearest-even: add last bit after truncated mantissa
+  const uint32_t b = *((uint32_t*)&f) + 0x00001000;
+
+  const uint32_t e = (b & 0x7F800000) >> 23; // exponent
+  const uint32_t m = b & 0x007FFFFF; // mantissa
+
+  // sign : normalized : denormalized : saturate
+  return (b & 0x80000000) >> 16
+            | (e > 112) * ((((e - 112) << 10) & 0x7C00) | m >> 13)
+            | ((e < 113) & (e > 101)) * ((((0x007FF000 + m) >> (125 - e)) + 1) >> 1)
+            | (e > 143) * 0x7FFF;
 }
 
-constexpr size_t kRecoveryFactorPrecision = 10;
-constexpr size_t kRecoveryFactorNumEntries = 1 << kRecoveryFactorPrecision;
-struct RecoveryLUT {
-  RecoveryLUT(jr_metadata_ptr metadata) {
-    for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kRecoveryFactorNumEntries - 1);
+constexpr size_t kGainFactorPrecision = 10;
+constexpr size_t kGainFactorNumEntries = 1 << kGainFactorPrecision;
+struct GainLUT {
+  GainLUT(ultrahdr_metadata_ptr metadata) {
+    for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
+      float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
       float logBoost = log2(metadata->minContentBoost) * (1.0f - value)
                      + log2(metadata->maxContentBoost) * value;
-      mRecoveryTable[idx] = exp2(logBoost);
+      mGainTable[idx] = exp2(logBoost);
     }
   }
 
-  ~RecoveryLUT() {
+  GainLUT(ultrahdr_metadata_ptr metadata, float displayBoost) {
+    float boostFactor = displayBoost > 0 ? displayBoost / metadata->maxContentBoost : 1.0f;
+    for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
+      float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
+      float logBoost = log2(metadata->minContentBoost) * (1.0f - value)
+                     + log2(metadata->maxContentBoost) * value;
+      mGainTable[idx] = exp2(logBoost * boostFactor);
+    }
   }
 
-  float getRecoveryFactor(float recovery) {
-    uint32_t idx = static_cast<uint32_t>(recovery * (kRecoveryFactorNumEntries - 1));
+  ~GainLUT() {
+  }
+
+  float getGainFactor(float gain) {
+    uint32_t idx = static_cast<uint32_t>(gain * (kGainFactorNumEntries - 1));
     //TODO() : Remove once conversion modules have appropriate clamping in place
-    idx = CLIP3(idx, 0, kRecoveryFactorNumEntries - 1);
-    return mRecoveryTable[idx];
+    idx = CLIP3(idx, 0, kGainFactorNumEntries - 1);
+    return mGainTable[idx];
   }
 
 private:
-  float mRecoveryTable[kRecoveryFactorNumEntries];
+  float mGainTable[kGainFactorNumEntries];
 };
 
 struct ShepardsIDW {
@@ -179,11 +195,11 @@
   // p60 p61 p62 p63 p64 p65 p66 p67
   // p70 p71 p72 p73 p74 p75 p76 p77
 
-  // Recovery Map (for 4 scale factor) :-
+  // Gain Map (for 4 scale factor) :-
   // m00 p01
   // m10 m11
 
-  // Recovery sample of curr 4x4, right 4x4, bottom 4x4, bottom right 4x4 are used during
+  // Gain sample of curr 4x4, right 4x4, bottom 4x4, bottom right 4x4 are used during
   // reconstruction. hence table weight size is 4.
   float* mWeights;
   // TODO: check if its ok to mWeights at places
@@ -338,26 +354,29 @@
 inline Color identityConversion(Color e) { return e; }
 
 /*
- * Get the conversion to apply to the HDR image for recovery map generation
+ * Get the conversion to apply to the HDR image for gain map generation
  */
-ColorTransformFn getHdrConversionFn(jpegr_color_gamut sdr_gamut, jpegr_color_gamut hdr_gamut);
+ColorTransformFn getHdrConversionFn(ultrahdr_color_gamut sdr_gamut, ultrahdr_color_gamut hdr_gamut);
 
 
 ////////////////////////////////////////////////////////////////////////////////
-// Recovery map calculations
+// Gain map calculations
 
 /*
- * Calculate the 8-bit unsigned integer recovery value for the given SDR and HDR
+ * Calculate the 8-bit unsigned integer gain value for the given SDR and HDR
  * luminances in linear space, and the hdr ratio to encode against.
  */
-uint8_t encodeRecovery(float y_sdr, float y_hdr, jr_metadata_ptr metadata);
+uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata);
+uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata,
+                   float log2MinContentBoost, float log2MaxContentBoost);
 
 /*
- * Calculates the linear luminance in nits after applying the given recovery
+ * Calculates the linear luminance in nits after applying the given gain
  * value, with the given hdr ratio, to the given sdr input in the range [0, 1].
  */
-Color applyRecovery(Color e, float recovery, jr_metadata_ptr metadata);
-Color applyRecoveryLUT(Color e, float recovery, RecoveryLUT& recoveryLUT);
+Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata);
+Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata, float displayBoost);
+Color applyGainLUT(Color e, float gain, GainLUT& gainLUT);
 
 /*
  * Helper for sampling from YUV 420 images.
@@ -386,7 +405,7 @@
 Color sampleP010(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
 
 /*
- * Sample the recovery value for the map from a given x,y coordinate on a scale
+ * Sample the gain value for the map from a given x,y coordinate on a scale
  * that is map scale factor larger than the map size.
  */
 float sampleMap(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_t y);
@@ -407,6 +426,6 @@
  */
 uint64_t colorToRgbaF16(Color e_gamma);
 
-} // namespace android::jpegrecoverymap
+} // namespace android::ultrahdr
 
-#endif // ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H
+#endif // ANDROID_ULTRAHDR_RECOVERYMAPMATH_H
diff --git a/libs/ultrahdr/include/ultrahdr/icc.h b/libs/ultrahdr/include/ultrahdr/icc.h
new file mode 100644
index 0000000..7f6ab88
--- /dev/null
+++ b/libs/ultrahdr/include/ultrahdr/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_ULTRAHDR_ICC_H
+#define ANDROID_ULTRAHDR_ICC_H
+
+#include <ultrahdr/jpegr.h>
+#include <ultrahdr/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::ultrahdr {
+
+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 ultrahdr_transfer_function tf,
+                                       const ultrahdr_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 ultrahdr_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 ultrahdr_transfer_function tf,
+                                          const ultrahdr_color_gamut gamut);
+};
+}  // namespace android::ultrahdr
+
+#endif //ANDROID_ULTRAHDR_ICC_H
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
similarity index 94%
rename from libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoderhelper.h
rename to libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
index 8748237..f642bad 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoderhelper.h
+++ b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_JPEGRECOVERYMAP_JPEGDECODERHELPER_H
-#define ANDROID_JPEGRECOVERYMAP_JPEGDECODERHELPER_H
+#ifndef ANDROID_ULTRAHDR_JPEGDECODERHELPER_H
+#define ANDROID_ULTRAHDR_JPEGDECODERHELPER_H
 
 // We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
 #include <cstdio>
@@ -25,7 +25,7 @@
 }
 #include <utils/Errors.h>
 #include <vector>
-namespace android::jpegrecoverymap {
+namespace android::ultrahdr {
 /*
  * Encapsulates a converter from JPEG to raw image (YUV420planer or grey-scale) format.
  * This class is not thread-safe.
@@ -115,6 +115,6 @@
     // Position of EXIF package, default value is -1 which means no EXIF package appears.
     size_t mExifPos;
 };
-} /* namespace android::jpegrecoverymap  */
+} /* namespace android::ultrahdr  */
 
-#endif // ANDROID_JPEGRECOVERYMAP_JPEGDECODERHELPER_H
+#endif // ANDROID_ULTRAHDR_JPEGDECODERHELPER_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h
similarity index 89%
rename from libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoderhelper.h
rename to libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h
index 8b82b2b..2c6778e 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoderhelper.h
+++ b/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_JPEGRECOVERYMAP_JPEGENCODERHELPER_H
-#define ANDROID_JPEGRECOVERYMAP_JPEGENCODERHELPER_H
+#ifndef ANDROID_ULTRAHDR_JPEGENCODERHELPER_H
+#define ANDROID_ULTRAHDR_JPEGENCODERHELPER_H
 
 // We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
 #include <cstdio>
@@ -28,7 +28,7 @@
 #include <utils/Errors.h>
 #include <vector>
 
-namespace android::jpegrecoverymap {
+namespace android::ultrahdr {
 
 /*
  * Encapsulates a converter from raw image (YUV420planer or grey-scale) to JPEG format.
@@ -61,6 +61,11 @@
      */
     size_t getCompressedImageSize();
 
+    /*
+     * Process 16 lines of Y and 16 lines of U/V each time.
+     * We must pass at least 16 scanlines according to libjpeg documentation.
+     */
+    static const int kCompressBatchSize = 16;
 private:
     // initDestination(), emptyOutputBuffer() and emptyOutputBuffer() are callback functions to be
     // passed into jpeg library.
@@ -82,14 +87,11 @@
 
     // The block size for encoded jpeg image buffer.
     static const int kBlockSize = 16384;
-    // Process 16 lines of Y and 16 lines of U/V each time.
-    // We must pass at least 16 scanlines according to libjpeg documentation.
-    static const int kCompressBatchSize = 16;
 
     // The buffer that holds the compressed result.
     std::vector<JOCTET> mResultBuffer;
 };
 
-} /* namespace android::jpegrecoverymap  */
+} /* namespace android::ultrahdr  */
 
-#endif // ANDROID_JPEGRECOVERYMAP_JPEGENCODERHELPER_H
+#endif // ANDROID_ULTRAHDR_JPEGENCODERHELPER_H
diff --git a/libs/ultrahdr/include/ultrahdr/jpegr.h b/libs/ultrahdr/include/ultrahdr/jpegr.h
new file mode 100644
index 0000000..88038f1
--- /dev/null
+++ b/libs/ultrahdr/include/ultrahdr/jpegr.h
@@ -0,0 +1,388 @@
+/*
+ * 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_ULTRAHDR_JPEGR_H
+#define ANDROID_ULTRAHDR_JPEGR_H
+
+#include "jpegrerrorcode.h"
+#include "ultrahdr.h"
+
+#ifndef FLT_MAX
+#define FLT_MAX 0x1.fffffep127f
+#endif
+
+namespace android::ultrahdr {
+
+struct jpegr_info_struct {
+    size_t width;
+    size_t height;
+    std::vector<uint8_t>* iccData;
+    std::vector<uint8_t>* exifData;
+};
+
+/*
+ * Holds information for uncompressed image or gain map.
+ */
+struct jpegr_uncompressed_struct {
+    // Pointer to the data location.
+    void* data;
+    // Width of the gain map or the luma plane of the image in pixels.
+    int width;
+    // Height of the gain map or the luma plane of the image in pixels.
+    int height;
+    // Color gamut.
+    ultrahdr_color_gamut colorGamut;
+
+    // Values below are optional
+    // Pointer to chroma data, if it's NULL, chroma plane is considered to be immediately
+    // following after the luma plane.
+    // Note: currently this feature is only supported for P010 image (HDR input).
+    void* chroma_data = nullptr;
+    // Strides of Y plane in number of pixels, using 0 to present uninitialized, must be
+    // larger than or equal to luma width.
+    // Note: currently this feature is only supported for P010 image (HDR input).
+    int luma_stride = 0;
+    // Strides of UV plane in number of pixels, using 0 to present uninitialized, must be
+    // larger than or equal to chroma width.
+    // Note: currently this feature is only supported for P010 image (HDR input).
+    int chroma_stride = 0;
+};
+
+/*
+ * Holds information for compressed image or gain map.
+ */
+struct jpegr_compressed_struct {
+    // Pointer to the data location.
+    void* data;
+    // Used data length in bytes.
+    int length;
+    // Maximum available data length in bytes.
+    int maxLength;
+    // Color gamut.
+    ultrahdr_color_gamut colorGamut;
+};
+
+/*
+ * Holds information for EXIF metadata.
+ */
+struct jpegr_exif_struct {
+    // Pointer to the data location.
+    void* data;
+    // Data length;
+    int length;
+};
+
+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_info_struct* jr_info_ptr;
+
+class JpegR {
+public:
+    /*
+     * Experimental only
+     *
+     * Encode API-0
+     * Compress JPEGR image from 10-bit HDR YUV.
+     *
+     * Tonemap the HDR input to a SDR image, generate gain map from the HDR and SDR images,
+     * compress SDR YUV to 8-bit JPEG and append the gain map to the end of the compressed
+     * JPEG.
+     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
+     * @param hdr_tf transfer function of the HDR image
+     * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
+     *             represents the maximum available size of the desitination buffer, and it must be
+     *             set before calling this method. If the encoded JPEGR size exceeds
+     *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
+     * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is
+     *                the highest quality
+     * @param exif pointer to the exif metadata.
+     * @return NO_ERROR if encoding succeeds, error code if error occurs.
+     */
+    status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+                         ultrahdr_transfer_function hdr_tf,
+                         jr_compressed_ptr dest,
+                         int quality,
+                         jr_exif_ptr exif);
+
+    /*
+     * Encode API-1
+     * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV.
+     *
+     * Generate gain map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append
+     * the gain map to the end of the compressed JPEG. HDR and SDR inputs must be the same
+     * resolution.
+     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
+     * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
+     * @param hdr_tf transfer function of the HDR image
+     * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
+     *             represents the maximum available size of the desitination buffer, and it must be
+     *             set before calling this method. If the encoded JPEGR size exceeds
+     *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
+     * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is
+     *                the highest quality
+     * @param exif pointer to the exif metadata.
+     * @return NO_ERROR if encoding succeeds, error code if error occurs.
+     */
+    status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+                         jr_uncompressed_ptr uncompressed_yuv_420_image,
+                         ultrahdr_transfer_function hdr_tf,
+                         jr_compressed_ptr dest,
+                         int quality,
+                         jr_exif_ptr exif);
+
+    /*
+     * Encode API-2
+     * Compress JPEGR image from 10-bit HDR YUV, 8-bit SDR YUV and compressed 8-bit JPEG.
+     *
+     * This method requires HAL Hardware JPEG encoder.
+     *
+     * Generate gain map from the HDR and SDR inputs, append the gain map to the end of the
+     * compressed JPEG. HDR and SDR inputs must be the same resolution and color space.
+     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
+     * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
+     *                                   Note: the SDR image must be the decoded version of the JPEG
+     *                                         input
+     * @param compressed_jpeg_image compressed 8-bit JPEG image
+     * @param hdr_tf transfer function of the HDR image
+     * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
+     *             represents the maximum available size of the desitination buffer, and it must be
+     *             set before calling this method. If the encoded JPEGR size exceeds
+     *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
+     * @return NO_ERROR if encoding succeeds, error code if error occurs.
+     */
+    status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+                         jr_uncompressed_ptr uncompressed_yuv_420_image,
+                         jr_compressed_ptr compressed_jpeg_image,
+                         ultrahdr_transfer_function hdr_tf,
+                         jr_compressed_ptr dest);
+
+    /*
+     * Encode API-3
+     * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV.
+     *
+     * This method requires HAL Hardware JPEG encoder.
+     *
+     * Decode the compressed 8-bit JPEG image to YUV SDR, generate gain map from the HDR input
+     * and the decoded SDR result, append the gain map to the end of the compressed JPEG. HDR
+     * and SDR inputs must be the same resolution.
+     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
+     * @param compressed_jpeg_image compressed 8-bit JPEG image
+     * @param hdr_tf transfer function of the HDR image
+     * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
+     *             represents the maximum available size of the desitination buffer, and it must be
+     *             set before calling this method. If the encoded JPEGR size exceeds
+     *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
+     * @return NO_ERROR if encoding succeeds, error code if error occurs.
+     */
+    status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+                         jr_compressed_ptr compressed_jpeg_image,
+                         ultrahdr_transfer_function hdr_tf,
+                         jr_compressed_ptr dest);
+
+    /*
+     * Encode API-4
+     * Assemble JPEGR image from SDR JPEG and gainmap JPEG.
+     *
+     * Assemble the primary JPEG image, the gain map and the metadata to JPEG/R format.
+     * @param compressed_jpeg_image compressed 8-bit JPEG image
+     * @param compressed_gainmap compressed 8-bit JPEG single channel image
+     * @param metadata metadata to be written in XMP of the primary jpeg
+     * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
+     *             represents the maximum available size of the desitination buffer, and it must be
+     *             set before calling this method. If the encoded JPEGR size exceeds
+     *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
+     * @return NO_ERROR if encoding succeeds, error code if error occurs.
+     */
+    status_t encodeJPEGR(jr_compressed_ptr compressed_jpeg_image,
+                         jr_compressed_ptr compressed_gainmap,
+                         ultrahdr_metadata_ptr metadata,
+                         jr_compressed_ptr dest);
+
+    /*
+     * Decode API
+     * Decompress JPEGR image.
+     *
+     * @param compressed_jpegr_image compressed JPEGR image.
+     * @param dest destination of the uncompressed JPEGR image.
+     * @param max_display_boost (optional) the maximum available boost supported by a display,
+     *                          the value must be greater than or equal to 1.0.
+     * @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 gain_map destination of the decoded gain map. The default value is NULL where
+                           the decoder will do nothing about it. If configured not NULL the decoder
+                           will write the decoded gain_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 ultrahdr_metadata_struct}.
+     * @return NO_ERROR if decoding succeeds, error code if error occurs.
+     */
+    status_t decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
+                         jr_uncompressed_ptr dest,
+                         float max_display_boost = FLT_MAX,
+                         jr_exif_ptr exif = nullptr,
+                         ultrahdr_output_format output_format = ULTRAHDR_OUTPUT_HDR_LINEAR,
+                         jr_uncompressed_ptr gain_map = nullptr,
+                         ultrahdr_metadata_ptr metadata = nullptr);
+
+    /*
+    * Gets Info from JPEGR file without decoding it.
+    *
+    * The output is filled jpegr_info structure
+    * @param compressed_jpegr_image compressed JPEGR image
+    * @param jpegr_info pointer to output JPEGR info. Members of jpegr_info
+    *         are owned by the caller
+    * @return NO_ERROR if JPEGR parsing succeeds, error code otherwise
+    */
+    status_t getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
+                          jr_info_ptr jpegr_info);
+protected:
+    /*
+     * This method is called in the encoding pipeline. It will take the uncompressed 8-bit and
+     * 10-bit yuv images as input, and calculate the uncompressed gain map. The input images
+     * must be the same resolution.
+     *
+     * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
+     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
+     * @param hdr_tf transfer function of the HDR image
+     * @param dest gain map; caller responsible for memory of data
+     * @param metadata max_content_boost is filled in
+     * @return NO_ERROR if calculation succeeds, error code if error occurs.
+     */
+    status_t generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
+                             jr_uncompressed_ptr uncompressed_p010_image,
+                             ultrahdr_transfer_function hdr_tf,
+                             ultrahdr_metadata_ptr metadata,
+                             jr_uncompressed_ptr dest);
+
+    /*
+     * This method is called in the decoding pipeline. It will take the uncompressed (decoded)
+     * 8-bit yuv image, the uncompressed (decoded) gain map, and extracted JPEG/R metadata as
+     * input, and calculate the 10-bit recovered image. The recovered output image is the same
+     * color gamut as the SDR image, with HLG transfer function, and is in RGBA1010102 data format.
+     *
+     * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
+     * @param uncompressed_gain_map uncompressed gain 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 max_display_boost the maximum available boost supported by a display
+     * @param dest reconstructed HDR image
+     * @return NO_ERROR if calculation succeeds, error code if error occurs.
+     */
+    status_t applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
+                          jr_uncompressed_ptr uncompressed_gain_map,
+                          ultrahdr_metadata_ptr metadata,
+                          ultrahdr_output_format output_format,
+                          float max_display_boost,
+                          jr_uncompressed_ptr dest);
+
+private:
+    /*
+     * This method is called in the encoding pipeline. It will encode the gain map.
+     *
+     * @param uncompressed_gain_map uncompressed gain map
+     * @param dest encoded recover map
+     * @return NO_ERROR if encoding succeeds, error code if error occurs.
+     */
+    status_t compressGainMap(jr_uncompressed_ptr uncompressed_gain_map,
+                             jr_compressed_ptr dest);
+
+    /*
+     * This methoud is called to separate primary image and gain map image from JPEGR
+     *
+     * @param compressed_jpegr_image compressed JPEGR image
+     * @param primary_image destination of primary image
+     * @param gain_map destination of compressed gain map
+     * @return NO_ERROR if calculation succeeds, error code if error occurs.
+    */
+    status_t extractPrimaryImageAndGainMap(jr_compressed_ptr compressed_jpegr_image,
+                                           jr_compressed_ptr primary_image,
+                                           jr_compressed_ptr gain_map);
+    /*
+     * This method is called in the decoding pipeline. It will read XMP metadata to find the start
+     * position of the compressed gain map, and will extract the compressed gain map.
+     *
+     * @param compressed_jpegr_image compressed JPEGR image
+     * @param dest destination of compressed gain map
+     * @return NO_ERROR if calculation succeeds, error code if error occurs.
+     */
+    status_t extractGainMap(jr_compressed_ptr compressed_jpegr_image,
+                            jr_compressed_ptr dest);
+
+    /*
+     * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image,
+     * the compressed gain map and optionally the exif package as inputs, and generate the XMP
+     * metadata, and finally append everything in the order of:
+     *     SOI, APP2(EXIF) (if EXIF is from outside), APP2(XMP), primary image, gain map
+     * Note that EXIF package is only available for encoding API-0 and API-1. For encoding API-2 and
+     * API-3 this parameter is null, but the primary image in JPEG/R may still have EXIF as long as
+     * the input JPEG has EXIF.
+     *
+     * @param compressed_jpeg_image compressed 8-bit JPEG image
+     * @param compress_gain_map compressed recover map
+     * @param (nullable) exif EXIF package
+     * @param metadata JPEG/R metadata to encode in XMP of the jpeg
+     * @param dest compressed JPEGR image
+     * @return NO_ERROR if calculation succeeds, error code if error occurs.
+     */
+    status_t appendGainMap(jr_compressed_ptr compressed_jpeg_image,
+                           jr_compressed_ptr compressed_gain_map,
+                           jr_exif_ptr exif,
+                           ultrahdr_metadata_ptr metadata,
+                           jr_compressed_ptr dest);
+
+    /*
+     * This method will tone map a HDR image to an SDR image.
+     *
+     * @param src (input) uncompressed P010 image
+     * @param dest (output) tone mapping result as a YUV_420 image
+     * @return NO_ERROR if calculation succeeds, error code if error occurs.
+     */
+    status_t toneMap(jr_uncompressed_ptr src,
+                     jr_uncompressed_ptr dest);
+
+    /*
+     * This method will check the validity of the input images.
+     *
+     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
+     * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
+     * @return NO_ERROR if the input images are valid, error code is not valid.
+     */
+    status_t areInputImagesValid(jr_uncompressed_ptr uncompressed_p010_image,
+                                 jr_uncompressed_ptr uncompressed_yuv_420_image);
+};
+
+} // namespace android::ultrahdr
+
+#endif // ANDROID_ULTRAHDR_JPEGR_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h b/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h
similarity index 89%
rename from libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
rename to libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h
index f730343..9f59c3e 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
+++ b/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h
@@ -14,9 +14,12 @@
  * limitations under the License.
  */
 
+#ifndef ANDROID_ULTRAHDR_JPEGRERRORCODE_H
+#define ANDROID_ULTRAHDR_JPEGRERRORCODE_H
+
 #include <utils/Errors.h>
 
-namespace android::jpegrecoverymap {
+namespace android::ultrahdr {
 
 enum {
     // status_t map for errors in the media framework
@@ -46,6 +49,10 @@
     ERROR_JPEGR_CALCULATION_ERROR       = JPEGR_RUNTIME_ERROR_BASE - 3,
     ERROR_JPEGR_METADATA_ERROR          = JPEGR_RUNTIME_ERROR_BASE - 4,
     ERROR_JPEGR_TONEMAP_ERROR           = JPEGR_RUNTIME_ERROR_BASE - 5,
+
+    ERROR_JPEGR_UNSUPPORTED_FEATURE     = -20000,
 };
 
-}  // namespace android::jpegrecoverymap
+}  // namespace android::ultrahdr
+
+#endif // ANDROID_ULTRAHDR_JPEGRERRORCODE_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h b/libs/ultrahdr/include/ultrahdr/jpegrutils.h
similarity index 71%
rename from libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h
rename to libs/ultrahdr/include/ultrahdr/jpegrutils.h
index 4145853..4ab664e 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h
+++ b/libs/ultrahdr/include/ultrahdr/jpegrutils.h
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H
-#define ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H
+#ifndef ANDROID_ULTRAHDR_JPEGRUTILS_H
+#define ANDROID_ULTRAHDR_JPEGRUTILS_H
 
-#include <jpegrecoverymap/jpegr.h>
+#include <ultrahdr/jpegr.h>
 #include <utils/RefBase.h>
 
 #include <sstream>
@@ -25,9 +25,27 @@
 #include <string>
 #include <cstdio>
 
-namespace android::jpegrecoverymap {
+namespace android::ultrahdr {
 
-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 ultrahdr_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, ultrahdr_metadata_struct* metadata);
 
 /*
  * This method generates XMP metadata for the primary image.
@@ -84,17 +102,21 @@
  *     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
  *     <rdf:Description
  *       xmlns:Container="http://ns.google.com/photos/1.0/container/"
- *       xmlns:Item="http://ns.google.com/photos/1.0/container/item/">
+ *       xmlns:Item="http://ns.google.com/photos/1.0/container/item/"
+ *       xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/"
+ *       hdrgm:Version="1">
  *       <Container:Directory>
  *         <rdf:Seq>
- *           <rdf:li>
+ *           <rdf:li
+ *             rdf:parseType="Resource">
  *             <Container:Item
  *               Item:Semantic="Primary"
  *               Item:Mime="image/jpeg"/>
  *           </rdf:li>
- *           <rdf:li>
+ *           <rdf:li
+ *             rdf:parseType="Resource">
  *             <Container:Item
- *               Item:Semantic="RecoveryMap"
+ *               Item:Semantic="GainMap"
  *               Item:Mime="image/jpeg"
  *               Item:Length="1000"/>
  *           </rdf:li>
@@ -107,7 +129,8 @@
  * @param secondary_image_length length of secondary image
  * @return XMP metadata in type of string
  */
-std::string generateXmpForPrimaryImage(int secondary_image_length);
+std::string generateXmpForPrimaryImage(int secondary_image_length,
+                                       ultrahdr_metadata_struct& metadata);
 
 /*
  * This method generates XMP metadata for the recovery map image.
@@ -124,21 +147,21 @@
  *     <rdf:Description
  *       xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/"
  *       hdrgm:Version="1"
- *       hdrgm:GainMapMin="0.5"
- *       hdrgm:GainMapMax="8.5"
+ *       hdrgm:GainMapMin="-1"
+ *       hdrgm:GainMapMax="3"
  *       hdrgm:Gamma="1"
  *       hdrgm:OffsetSDR="0"
  *       hdrgm:OffsetHDR="0"
- *       hdrgm:HDRCapacityMin="0.5"
- *       hdrgm:HDRCapacityMax="8.5"
- *       hdrgm:BaseRendition="SDR"/>
+ *       hdrgm:HDRCapacityMin="0"
+ *       hdrgm:HDRCapacityMax="3"
+ *       hdrgm:BaseRenditionIsHDR="False"/>
  *   </rdf:RDF>
  * </x:xmpmeta>
  *
  * @param metadata JPEG/R metadata to encode as XMP
  * @return XMP metadata in type of string
  */
- std::string generateXmpForSecondaryImage(jpegr_metadata& metadata);
-}  // namespace android::jpegrecoverymap
+ std::string generateXmpForSecondaryImage(ultrahdr_metadata_struct& metadata);
+}  // namespace android::ultrahdr
 
-#endif //ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H
+#endif //ANDROID_ULTRAHDR_JPEGRUTILS_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h b/libs/ultrahdr/include/ultrahdr/multipictureformat.h
similarity index 70%
rename from libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h
rename to libs/ultrahdr/include/ultrahdr/multipictureformat.h
index 7dca916..c5bd09d 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h
+++ b/libs/ultrahdr/include/ultrahdr/multipictureformat.h
@@ -14,30 +14,18 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_JPEGRECOVERYMAP_MULTIPICTUREFORMAT_H
-#define ANDROID_JPEGRECOVERYMAP_MULTIPICTUREFORMAT_H
+#ifndef ANDROID_ULTRAHDR_MULTIPICTUREFORMAT_H
+#define ANDROID_ULTRAHDR_MULTIPICTUREFORMAT_H
 
-#include <jpegrecoverymap/jpegrutils.h>
+#include <ultrahdr/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::ultrahdr {
+
 constexpr size_t kNumPictures = 2;
 constexpr size_t kMpEndianSize = 4;
 constexpr uint16_t kTagSerializedCount = 3;
@@ -71,6 +59,6 @@
 sp<DataStruct> generateMpf(int primary_image_size, int primary_image_offset,
                            int secondary_image_size, int secondary_image_offset);
 
-}  // namespace android::jpegrecoverymap
+}  // namespace android::ultrahdr
 
-#endif //ANDROID_JPEGRECOVERYMAP_MULTIPICTUREFORMAT_H
+#endif //ANDROID_ULTRAHDR_MULTIPICTUREFORMAT_H
diff --git a/libs/ultrahdr/include/ultrahdr/ultrahdr.h b/libs/ultrahdr/include/ultrahdr/ultrahdr.h
new file mode 100644
index 0000000..f970936
--- /dev/null
+++ b/libs/ultrahdr/include/ultrahdr/ultrahdr.h
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_ULTRAHDR_ULTRAHDR_H
+#define ANDROID_ULTRAHDR_ULTRAHDR_H
+
+namespace android::ultrahdr {
+// Color gamuts for image data
+typedef enum {
+  ULTRAHDR_COLORGAMUT_UNSPECIFIED,
+  ULTRAHDR_COLORGAMUT_BT709,
+  ULTRAHDR_COLORGAMUT_P3,
+  ULTRAHDR_COLORGAMUT_BT2100,
+} ultrahdr_color_gamut;
+
+// Transfer functions for image data
+typedef enum {
+  ULTRAHDR_TF_UNSPECIFIED = -1,
+  ULTRAHDR_TF_LINEAR = 0,
+  ULTRAHDR_TF_HLG = 1,
+  ULTRAHDR_TF_PQ = 2,
+  ULTRAHDR_TF_SRGB = 3,
+} ultrahdr_transfer_function;
+
+// Target output formats for decoder
+typedef enum {
+  ULTRAHDR_OUTPUT_SDR,          // SDR in RGBA_8888 color format
+  ULTRAHDR_OUTPUT_HDR_LINEAR,   // HDR in F16 color format (linear)
+  ULTRAHDR_OUTPUT_HDR_PQ,       // HDR in RGBA_1010102 color format (PQ transfer function)
+  ULTRAHDR_OUTPUT_HDR_HLG,      // HDR in RGBA_1010102 color format (HLG transfer function)
+} ultrahdr_output_format;
+
+/*
+ * Holds information for gain map related metadata.
+ */
+struct ultrahdr_metadata_struct {
+  // Ultra HDR library version
+  const char* version;
+  // Max Content Boost for the map
+  float maxContentBoost;
+  // Min Content Boost for the map
+  float minContentBoost;
+};
+typedef struct ultrahdr_metadata_struct* ultrahdr_metadata_ptr;
+
+}  // namespace android::ultrahdr
+
+#endif //ANDROID_ULTRAHDR_ULTRAHDR_H
diff --git a/libs/jpegrecoverymap/jpegdecoderhelper.cpp b/libs/ultrahdr/jpegdecoderhelper.cpp
similarity index 98%
rename from libs/jpegrecoverymap/jpegdecoderhelper.cpp
rename to libs/ultrahdr/jpegdecoderhelper.cpp
index d36bbf8..12217b7 100644
--- a/libs/jpegrecoverymap/jpegdecoderhelper.cpp
+++ b/libs/ultrahdr/jpegdecoderhelper.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include <jpegrecoverymap/jpegdecoderhelper.h>
+#include <ultrahdr/jpegdecoderhelper.h>
 
 #include <utils/Log.h>
 
@@ -24,7 +24,7 @@
 
 using namespace std;
 
-namespace android::jpegrecoverymap {
+namespace android::ultrahdr {
 
 const uint32_t kAPP0Marker = JPEG_APP0;      // JFIF
 const uint32_t kAPP1Marker = JPEG_APP0 + 1;  // EXIF, XMP
@@ -413,4 +413,4 @@
     return true;
 }
 
-} // namespace jpegrecoverymap
+} // namespace ultrahdr
diff --git a/libs/jpegrecoverymap/jpegencoderhelper.cpp b/libs/ultrahdr/jpegencoderhelper.cpp
similarity index 96%
rename from libs/jpegrecoverymap/jpegencoderhelper.cpp
rename to libs/ultrahdr/jpegencoderhelper.cpp
index 586cd34..10a7630 100644
--- a/libs/jpegrecoverymap/jpegencoderhelper.cpp
+++ b/libs/ultrahdr/jpegencoderhelper.cpp
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-#include <jpegrecoverymap/jpegencoderhelper.h>
+#include <ultrahdr/jpegencoderhelper.h>
 
 #include <utils/Log.h>
 
 #include <errno.h>
 
-namespace android::jpegrecoverymap {
+namespace android::ultrahdr {
 
 // The destination manager that can access |mResultBuffer| in JpegEncoderHelper.
 struct destination_mgr {
@@ -38,11 +38,6 @@
 bool JpegEncoderHelper::compressImage(const void* image, int width, int height, int quality,
                                    const void* iccBuffer, unsigned int iccSize,
                                    bool isSingleChannel) {
-    if (width % 8 != 0 || height % 2 != 0) {
-        ALOGE("Image size can not be handled: %dx%d", width, height);
-        return false;
-    }
-
     mResultBuffer.clear();
     if (!encode(image, width, height, quality, iccBuffer, iccSize, isSingleChannel)) {
         return false;
@@ -236,4 +231,4 @@
     return true;
 }
 
-} // namespace jpegrecoverymap
+} // namespace ultrahdr
diff --git a/libs/jpegrecoverymap/jpegr.cpp b/libs/ultrahdr/jpegr.cpp
similarity index 67%
rename from libs/jpegrecoverymap/jpegr.cpp
rename to libs/ultrahdr/jpegr.cpp
index 79b1ae3..ef927e3 100644
--- a/libs/jpegrecoverymap/jpegr.cpp
+++ b/libs/ultrahdr/jpegr.cpp
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-#include <jpegrecoverymap/jpegr.h>
-#include <jpegrecoverymap/jpegencoderhelper.h>
-#include <jpegrecoverymap/jpegdecoderhelper.h>
-#include <jpegrecoverymap/recoverymapmath.h>
-#include <jpegrecoverymap/jpegrutils.h>
-#include <jpegrecoverymap/multipictureformat.h>
+#include <ultrahdr/jpegr.h>
+#include <ultrahdr/jpegencoderhelper.h>
+#include <ultrahdr/jpegdecoderhelper.h>
+#include <ultrahdr/gainmapmath.h>
+#include <ultrahdr/jpegrutils.h>
+#include <ultrahdr/multipictureformat.h>
+#include <ultrahdr/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>
@@ -46,14 +43,14 @@
 using namespace std;
 using namespace photos_editing_formats::image_io;
 
-namespace android::jpegrecoverymap {
+namespace android::ultrahdr {
 
 #define USE_SRGB_INVOETF_LUT 1
 #define USE_HLG_OETF_LUT 1
 #define USE_PQ_OETF_LUT 1
 #define USE_HLG_INVOETF_LUT 1
 #define USE_PQ_INVOETF_LUT 1
-#define USE_APPLY_RECOVERY_LUT 1
+#define USE_APPLY_GAIN_LUT 1
 
 #define JPEGR_CHECK(x)          \
   {                             \
@@ -64,15 +61,18 @@
   }
 
 // The current JPEGR version that we encode to
-static const uint32_t kJpegrVersion = 1;
+static const char* const kJpegrVersion = "1.0";
 
 // Map is quarter res / sixteenth size
 static const size_t kMapDimensionScaleFactor = 4;
 // JPEG block size.
-// JPEG encoding / decoding will require 8 x 8 DCT transform.
-// Width must be 8 dividable, and height must be 2 dividable.
-static const size_t kJpegBlock = 8;
-// JPEG compress quality (0 ~ 100) for recovery map
+// JPEG encoding / decoding will require block based DCT transform 16 x 16 for luma,
+// and 8 x 8 for chroma.
+// Width must be 16 dividable for luma, and 8 dividable for chroma.
+// If this criteria is not ficilitated, we will pad zeros based on the required block size.
+static const size_t kJpegBlock = JpegEncoderHelper::kCompressBatchSize;
+static const size_t kJpegBlockSquare = kJpegBlock * kJpegBlock;
+// JPEG compress quality (0 ~ 100) for gain map
 static const int kMapCompressQuality = 85;
 
 #define CONFIG_MULTITHREAD 1
@@ -89,24 +89,50 @@
   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},
-};
+status_t JpegR::areInputImagesValid(jr_uncompressed_ptr uncompressed_p010_image,
+                                    jr_uncompressed_ptr uncompressed_yuv_420_image) {
+  if (uncompressed_p010_image == nullptr) {
+    return ERROR_JPEGR_INVALID_NULL_PTR;
+  }
 
-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},
-};
+  if (uncompressed_p010_image->luma_stride != 0
+          && uncompressed_p010_image->luma_stride < uncompressed_p010_image->width) {
+    ALOGE("Image stride can not be smaller than width, stride=%d, width=%d",
+                uncompressed_p010_image->luma_stride, uncompressed_p010_image->width);
+    return ERROR_JPEGR_INVALID_INPUT_TYPE;
+  }
+
+  if (uncompressed_yuv_420_image == nullptr) {
+    return NO_ERROR;
+  }
+
+  if (uncompressed_yuv_420_image->luma_stride != 0) {
+    ALOGE("Stride is not supported for YUV420 image");
+    return ERROR_JPEGR_UNSUPPORTED_FEATURE;
+  }
+
+  if (uncompressed_yuv_420_image->chroma_data != nullptr) {
+    ALOGE("Pointer to chroma plane is not supported for YUV420 image, chroma data must"
+          "be immediately after the luma data.");
+    return ERROR_JPEGR_UNSUPPORTED_FEATURE;
+  }
+
+  if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
+      || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
+    ALOGE("Image resolutions mismatch: P010: %dx%d, YUV420: %dx%d",
+              uncompressed_p010_image->width,
+              uncompressed_p010_image->height,
+              uncompressed_yuv_420_image->width,
+              uncompressed_yuv_420_image->height);
+    return ERROR_JPEGR_RESOLUTION_MISMATCH;
+  }
+
+  return NO_ERROR;
+}
 
 /* Encode API-0 */
 status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
-                            jpegr_transfer_function hdr_tf,
+                            ultrahdr_transfer_function hdr_tf,
                             jr_compressed_ptr dest,
                             int quality,
                             jr_exif_ptr exif) {
@@ -118,24 +144,27 @@
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
 
-  if (uncompressed_p010_image->width % kJpegBlock != 0
-          || uncompressed_p010_image->height % 2 != 0) {
-    ALOGE("Image size can not be handled: %dx%d",
-            uncompressed_p010_image->width, uncompressed_p010_image->height);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
+  if (status_t ret = areInputImagesValid(
+          uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr) != NO_ERROR) {
+    return ret;
   }
 
-  jpegr_metadata metadata;
+  ultrahdr_metadata_struct metadata;
   metadata.version = kJpegrVersion;
 
   jpegr_uncompressed_struct uncompressed_yuv_420_image;
-  unique_ptr<uint8_t[]> uncompressed_yuv_420_image_data = make_unique<uint8_t[]>(
-      uncompressed_p010_image->width * uncompressed_p010_image->height * 3 / 2);
+  size_t gain_map_length = uncompressed_p010_image->width * uncompressed_p010_image->height * 3 / 2;
+  // Pad a pseudo chroma block (kJpegBlock / 2) x (kJpegBlock / 2)
+  // if width is not kJpegBlock aligned.
+  if (uncompressed_p010_image->width % kJpegBlock != 0) {
+    gain_map_length += kJpegBlockSquare / 4;
+  }
+  unique_ptr<uint8_t[]> uncompressed_yuv_420_image_data = make_unique<uint8_t[]>(gain_map_length);
   uncompressed_yuv_420_image.data = uncompressed_yuv_420_image_data.get();
   JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image));
 
   jpegr_uncompressed_struct map;
-  JPEGR_CHECK(generateRecoveryMap(
+  JPEGR_CHECK(generateGainMap(
       &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(map.data));
@@ -144,24 +173,23 @@
   compressed_map.maxLength = map.width * map.height;
   unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
   compressed_map.data = compressed_map_data.get();
-  JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
+  JPEGR_CHECK(compressGainMap(&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(ULTRAHDR_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;
   jpeg.data = jpeg_encoder.getCompressedImagePtr();
   jpeg.length = jpeg_encoder.getCompressedImageSize();
 
-  JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, exif, &metadata, dest));
+  JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, &metadata, dest));
 
   return NO_ERROR;
 }
@@ -169,7 +197,7 @@
 /* Encode API-1 */
 status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
                             jr_uncompressed_ptr uncompressed_yuv_420_image,
-                            jpegr_transfer_function hdr_tf,
+                            ultrahdr_transfer_function hdr_tf,
                             jr_compressed_ptr dest,
                             int quality,
                             jr_exif_ptr exif) {
@@ -183,23 +211,16 @@
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
 
-  if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
-   || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
-    return ERROR_JPEGR_RESOLUTION_MISMATCH;
+  if (status_t ret = areInputImagesValid(
+          uncompressed_p010_image, uncompressed_yuv_420_image) != NO_ERROR) {
+    return ret;
   }
 
-  if (uncompressed_p010_image->width % kJpegBlock != 0
-          || uncompressed_p010_image->height % 2 != 0) {
-    ALOGE("Image size can not be handled: %dx%d",
-            uncompressed_p010_image->width, uncompressed_p010_image->height);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-
-  jpegr_metadata metadata;
+  ultrahdr_metadata_struct metadata;
   metadata.version = kJpegrVersion;
 
   jpegr_uncompressed_struct map;
-  JPEGR_CHECK(generateRecoveryMap(
+  JPEGR_CHECK(generateGainMap(
       uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(map.data));
@@ -208,24 +229,23 @@
   compressed_map.maxLength = map.width * map.height;
   unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
   compressed_map.data = compressed_map_data.get();
-  JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
+  JPEGR_CHECK(compressGainMap(&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(ULTRAHDR_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;
   jpeg.data = jpeg_encoder.getCompressedImagePtr();
   jpeg.length = jpeg_encoder.getCompressedImageSize();
 
-  JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, exif, &metadata, dest));
+  JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, &metadata, dest));
 
   return NO_ERROR;
 }
@@ -234,7 +254,7 @@
 status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
                             jr_uncompressed_ptr uncompressed_yuv_420_image,
                             jr_compressed_ptr compressed_jpeg_image,
-                            jpegr_transfer_function hdr_tf,
+                            ultrahdr_transfer_function hdr_tf,
                             jr_compressed_ptr dest) {
   if (uncompressed_p010_image == nullptr
    || uncompressed_yuv_420_image == nullptr
@@ -243,23 +263,16 @@
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
-  if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
-   || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
-    return ERROR_JPEGR_RESOLUTION_MISMATCH;
+  if (status_t ret = areInputImagesValid(
+          uncompressed_p010_image, uncompressed_yuv_420_image) != NO_ERROR) {
+    return ret;
   }
 
-  if (uncompressed_p010_image->width % kJpegBlock != 0
-          || uncompressed_p010_image->height % 2 != 0) {
-    ALOGE("Image size can not be handled: %dx%d",
-            uncompressed_p010_image->width, uncompressed_p010_image->height);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-
-  jpegr_metadata metadata;
+  ultrahdr_metadata_struct metadata;
   metadata.version = kJpegrVersion;
 
   jpegr_uncompressed_struct map;
-  JPEGR_CHECK(generateRecoveryMap(
+  JPEGR_CHECK(generateGainMap(
       uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(map.data));
@@ -268,9 +281,9 @@
   compressed_map.maxLength = map.width * map.height;
   unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
   compressed_map.data = compressed_map_data.get();
-  JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
+  JPEGR_CHECK(compressGainMap(&map, &compressed_map));
 
-  JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest));
+  JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest));
 
   return NO_ERROR;
 }
@@ -278,7 +291,7 @@
 /* Encode API-3 */
 status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
                             jr_compressed_ptr compressed_jpeg_image,
-                            jpegr_transfer_function hdr_tf,
+                            ultrahdr_transfer_function hdr_tf,
                             jr_compressed_ptr dest) {
   if (uncompressed_p010_image == nullptr
    || compressed_jpeg_image == nullptr
@@ -286,11 +299,9 @@
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
-  if (uncompressed_p010_image->width % kJpegBlock != 0
-          || uncompressed_p010_image->height % 2 != 0) {
-    ALOGE("Image size can not be handled: %dx%d",
-            uncompressed_p010_image->width, uncompressed_p010_image->height);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
+  if (status_t ret = areInputImagesValid(
+          uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr) != NO_ERROR) {
+    return ret;
   }
 
   JpegDecoderHelper jpeg_decoder;
@@ -308,11 +319,11 @@
     return ERROR_JPEGR_RESOLUTION_MISMATCH;
   }
 
-  jpegr_metadata metadata;
+  ultrahdr_metadata_struct metadata;
   metadata.version = kJpegrVersion;
 
   jpegr_uncompressed_struct map;
-  JPEGR_CHECK(generateRecoveryMap(
+  JPEGR_CHECK(generateGainMap(
       &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(map.data));
@@ -321,21 +332,31 @@
   compressed_map.maxLength = map.width * map.height;
   unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
   compressed_map.data = compressed_map_data.get();
-  JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
+  JPEGR_CHECK(compressGainMap(&map, &compressed_map));
 
-  JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest));
+  JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest));
 
   return NO_ERROR;
 }
 
+/* Encode API-4 */
+status_t JpegR::encodeJPEGR(jr_compressed_ptr compressed_jpeg_image,
+                            jr_compressed_ptr compressed_gainmap,
+                            ultrahdr_metadata_ptr metadata,
+                            jr_compressed_ptr dest) {
+  JPEGR_CHECK(appendGainMap(compressed_jpeg_image, compressed_gainmap, /* exif */ nullptr,
+          metadata, dest));
+  return NO_ERROR;
+}
+
 status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, jr_info_ptr jpegr_info) {
   if (compressed_jpegr_image == nullptr || jpegr_info == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
-  jpegr_compressed_struct primary_image, recovery_map;
-  JPEGR_CHECK(extractPrimaryImageAndRecoveryMap(compressed_jpegr_image,
-                                                &primary_image, &recovery_map));
+  jpegr_compressed_struct primary_image, gain_map;
+  JPEGR_CHECK(extractPrimaryImageAndGainMap(compressed_jpegr_image,
+                                            &primary_image, &gain_map));
 
   JpegDecoderHelper jpeg_decoder;
   if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length,
@@ -350,15 +371,20 @@
 /* Decode API */
 status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
                             jr_uncompressed_ptr dest,
+                            float max_display_boost,
                             jr_exif_ptr exif,
-                            jpegr_output_format output_format) {
+                            ultrahdr_output_format output_format,
+                            jr_uncompressed_ptr gain_map,
+                            ultrahdr_metadata_ptr metadata) {
   if (compressed_jpegr_image == nullptr || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
-  // TODO: fill EXIF data
-  (void) exif;
 
-  if (output_format == JPEGR_OUTPUT_SDR) {
+  if (max_display_boost < 1.0f) {
+      return ERROR_JPEGR_INVALID_INPUT_TYPE;
+  }
+
+  if (output_format == ULTRAHDR_OUTPUT_SDR) {
     JpegDecoderHelper jpeg_decoder;
     if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length,
                                       true)) {
@@ -372,52 +398,99 @@
            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 (gain_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 (gain_map == nullptr) {
+      return NO_ERROR;
+    }
   }
 
   jpegr_compressed_struct compressed_map;
-  jpegr_metadata metadata;
-  JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
+  JPEGR_CHECK(extractGainMap(compressed_jpegr_image, &compressed_map));
+
+  JpegDecoderHelper gain_map_decoder;
+  if (!gain_map_decoder.decompressImage(compressed_map.data, compressed_map.length)) {
+    return ERROR_JPEGR_DECODE_ERROR;
+  }
+
+  if (gain_map != nullptr) {
+    gain_map->width = gain_map_decoder.getDecompressedImageWidth();
+    gain_map->height = gain_map_decoder.getDecompressedImageHeight();
+    int size = gain_map->width * gain_map->height;
+    gain_map->data = malloc(size);
+    memcpy(gain_map->data, gain_map_decoder.getDecompressedImagePtr(), size);
+  }
+
+  ultrahdr_metadata_struct uhdr_metadata;
+  if (!getMetadataFromXMP(static_cast<uint8_t*>(gain_map_decoder.getXMPPtr()),
+                          gain_map_decoder.getXMPSize(), &uhdr_metadata)) {
+    return ERROR_JPEGR_DECODE_ERROR;
+  }
+
+  if (metadata != nullptr) {
+      metadata->version = uhdr_metadata.version;
+      metadata->minContentBoost = uhdr_metadata.minContentBoost;
+      metadata->maxContentBoost = uhdr_metadata.maxContentBoost;
+  }
+
+  if (output_format == ULTRAHDR_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;
-  map.data = recovery_map_decoder.getDecompressedImagePtr();
-  map.width = recovery_map_decoder.getDecompressedImageWidth();
-  map.height = recovery_map_decoder.getDecompressedImageHeight();
+  map.data = gain_map_decoder.getDecompressedImagePtr();
+  map.width = gain_map_decoder.getDecompressedImageWidth();
+  map.height = gain_map_decoder.getDecompressedImageHeight();
 
   jpegr_uncompressed_struct uncompressed_yuv_420_image;
   uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
   uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
   uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
 
-  if (!getMetadataFromXMP(static_cast<uint8_t*>(recovery_map_decoder.getXMPPtr()),
-                          recovery_map_decoder.getXMPSize(), &metadata)) {
-    return ERROR_JPEGR_DECODE_ERROR;
-  }
-
-  JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, output_format, dest));
+  JPEGR_CHECK(applyGainMap(&uncompressed_yuv_420_image, &map, &uhdr_metadata, output_format,
+                           max_display_boost, dest));
   return NO_ERROR;
 }
 
-status_t JpegR::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
-                                    jr_compressed_ptr dest) {
-  if (uncompressed_recovery_map == nullptr || dest == nullptr) {
+status_t JpegR::compressGainMap(jr_uncompressed_ptr uncompressed_gain_map,
+                                jr_compressed_ptr dest) {
+  if (uncompressed_gain_map == nullptr || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
   JpegEncoderHelper jpeg_encoder;
-  if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data,
-                                  uncompressed_recovery_map->width,
-                                  uncompressed_recovery_map->height,
+  if (!jpeg_encoder.compressImage(uncompressed_gain_map->data,
+                                  uncompressed_gain_map->width,
+                                  uncompressed_gain_map->height,
                                   kMapCompressQuality,
                                   nullptr,
                                   0,
@@ -431,7 +504,7 @@
 
   memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize());
   dest->length = jpeg_encoder.getCompressedImageSize();
-  dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
+  dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
 
   return NO_ERROR;
 }
@@ -461,7 +534,7 @@
       if (mQueuedAllJobs) {
         return false;
       } else {
-        mCv.wait(lock);
+        mCv.wait_for(lock, std::chrono::milliseconds(100));
       }
     } else {
       auto it = mJobs.begin();
@@ -484,6 +557,8 @@
 void JobQueue::markQueueForEnd() {
   std::unique_lock<std::mutex> lock{mMutex};
   mQueuedAllJobs = true;
+  lock.unlock();
+  mCv.notify_all();
 }
 
 void JobQueue::reset() {
@@ -492,11 +567,11 @@
   mQueuedAllJobs = false;
 }
 
-status_t JpegR::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
-                                    jr_uncompressed_ptr uncompressed_p010_image,
-                                    jpegr_transfer_function hdr_tf,
-                                    jr_metadata_ptr metadata,
-                                    jr_uncompressed_ptr dest) {
+status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
+                                jr_uncompressed_ptr uncompressed_p010_image,
+                                ultrahdr_transfer_function hdr_tf,
+                                ultrahdr_metadata_ptr metadata,
+                                jr_uncompressed_ptr dest) {
   if (uncompressed_yuv_420_image == nullptr
    || uncompressed_p010_image == nullptr
    || metadata == nullptr
@@ -509,8 +584,8 @@
     return ERROR_JPEGR_RESOLUTION_MISMATCH;
   }
 
-  if (uncompressed_yuv_420_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED
-   || uncompressed_p010_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED) {
+  if (uncompressed_yuv_420_image->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED
+   || uncompressed_p010_image->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
     return ERROR_JPEGR_INVALID_COLORGAMUT;
   }
 
@@ -524,7 +599,7 @@
 
   dest->width = map_stride;
   dest->height = map_height_aligned;
-  dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
+  dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
   dest->data = new uint8_t[map_stride * map_height_aligned];
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
@@ -532,10 +607,10 @@
   ColorTransformFn hdrInvOetf = nullptr;
   float hdr_white_nits = 0.0f;
   switch (hdr_tf) {
-    case JPEGR_TF_LINEAR:
+    case ULTRAHDR_TF_LINEAR:
       hdrInvOetf = identityConversion;
       break;
-    case JPEGR_TF_HLG:
+    case ULTRAHDR_TF_HLG:
 #if USE_HLG_INVOETF_LUT
       hdrInvOetf = hlgInvOetfLUT;
 #else
@@ -543,7 +618,7 @@
 #endif
       hdr_white_nits = kHlgMaxNits;
       break;
-    case JPEGR_TF_PQ:
+    case ULTRAHDR_TF_PQ:
 #if USE_PQ_INVOETF_LUT
       hdrInvOetf = pqInvOetfLUT;
 #else
@@ -558,22 +633,24 @@
 
   metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits;
   metadata->minContentBoost = 1.0f;
+  float log2MinBoost = log2(metadata->minContentBoost);
+  float log2MaxBoost = log2(metadata->maxContentBoost);
 
   ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
       uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
 
   ColorCalculationFn luminanceFn = nullptr;
   switch (uncompressed_yuv_420_image->colorGamut) {
-    case JPEGR_COLORGAMUT_BT709:
+    case ULTRAHDR_COLORGAMUT_BT709:
       luminanceFn = srgbLuminance;
       break;
-    case JPEGR_COLORGAMUT_P3:
+    case ULTRAHDR_COLORGAMUT_P3:
       luminanceFn = p3Luminance;
       break;
-    case JPEGR_COLORGAMUT_BT2100:
+    case ULTRAHDR_COLORGAMUT_BT2100:
       luminanceFn = bt2100Luminance;
       break;
-    case JPEGR_COLORGAMUT_UNSPECIFIED:
+    case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
       // Should be impossible to hit after input validation.
       return ERROR_JPEGR_INVALID_COLORGAMUT;
   }
@@ -585,7 +662,8 @@
 
   std::function<void()> generateMap = [uncompressed_yuv_420_image, uncompressed_p010_image,
                                        metadata, dest, hdrInvOetf, hdrGamutConversionFn,
-                                       luminanceFn, hdr_white_nits, &jobQueue]() -> void {
+                                       luminanceFn, hdr_white_nits, log2MinBoost, log2MaxBoost,
+                                       &jobQueue]() -> void {
     size_t rowStart, rowEnd;
     size_t dest_map_width = uncompressed_yuv_420_image->width / kMapDimensionScaleFactor;
     size_t dest_map_stride = dest->width;
@@ -610,7 +688,7 @@
 
           size_t pixel_idx = x + y * dest_map_stride;
           reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
-              encodeRecovery(sdr_y_nits, hdr_y_nits, metadata);
+              encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost);
         }
       }
     }
@@ -636,13 +714,14 @@
   return NO_ERROR;
 }
 
-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) {
+status_t JpegR::applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
+                             jr_uncompressed_ptr uncompressed_gain_map,
+                             ultrahdr_metadata_ptr metadata,
+                             ultrahdr_output_format output_format,
+                             float max_display_boost,
+                             jr_uncompressed_ptr dest) {
   if (uncompressed_yuv_420_image == nullptr
-   || uncompressed_recovery_map == nullptr
+   || uncompressed_gain_map == nullptr
    || metadata == nullptr
    || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
@@ -651,13 +730,13 @@
   dest->width = uncompressed_yuv_420_image->width;
   dest->height = uncompressed_yuv_420_image->height;
   ShepardsIDW idwTable(kMapDimensionScaleFactor);
-  RecoveryLUT recoveryLUT(metadata);
+  float display_boost = std::min(max_display_boost, metadata->maxContentBoost);
+  GainLUT gainLUT(metadata, display_boost);
 
   JobQueue jobQueue;
-  std::function<void()> applyRecMap = [uncompressed_yuv_420_image, uncompressed_recovery_map,
+  std::function<void()> applyRecMap = [uncompressed_yuv_420_image, uncompressed_gain_map,
                                        metadata, dest, &jobQueue, &idwTable, output_format,
-                                       &recoveryLUT]() -> void {
-    const float hdr_ratio = metadata->maxContentBoost;
+                                       &gainLUT, display_boost]() -> void {
     size_t width = uncompressed_yuv_420_image->width;
     size_t height = uncompressed_yuv_420_image->height;
 
@@ -672,33 +751,34 @@
 #else
           Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
 #endif
-          float recovery;
+          float gain;
           // TODO: determine map scaling factor based on actual map dims
           size_t map_scale_factor = kMapDimensionScaleFactor;
           // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
           // Currently map_scale_factor is of type size_t, but it could be changed to a float
           // later.
           if (map_scale_factor != floorf(map_scale_factor)) {
-            recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y);
+            gain = sampleMap(uncompressed_gain_map, map_scale_factor, x, y);
           } else {
-            recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y, idwTable);
+            gain = sampleMap(uncompressed_gain_map, map_scale_factor, x, y, idwTable);
           }
-#if USE_APPLY_RECOVERY_LUT
-          Color rgb_hdr = applyRecoveryLUT(rgb_sdr, recovery, recoveryLUT);
+
+#if USE_APPLY_GAIN_LUT
+          Color rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT);
 #else
-          Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata);
+          Color rgb_hdr = applyGain(rgb_sdr, gain, metadata, display_boost);
 #endif
-          rgb_hdr = rgb_hdr / metadata->maxContentBoost;
+          rgb_hdr = rgb_hdr / display_boost;
           size_t pixel_idx = x + y * width;
 
           switch (output_format) {
-            case JPEGR_OUTPUT_HDR_LINEAR:
+            case ULTRAHDR_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:
+            case ULTRAHDR_OUTPUT_HDR_HLG:
             {
 #if USE_HLG_OETF_LUT
               ColorTransformFn hdrOetf = hlgOetfLUT;
@@ -710,7 +790,7 @@
               reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
               break;
             }
-            case JPEGR_OUTPUT_HDR_PQ:
+            case ULTRAHDR_OUTPUT_HDR_PQ:
             {
 #if USE_HLG_OETF_LUT
               ColorTransformFn hdrOetf = pqOetfLUT;
@@ -748,9 +828,9 @@
   return NO_ERROR;
 }
 
-status_t JpegR::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
-                                                  jr_compressed_ptr primary_image,
-                                                  jr_compressed_ptr recovery_map) {
+status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr compressed_jpegr_image,
+                                              jr_compressed_ptr primary_image,
+                                              jr_compressed_ptr gain_map) {
   if (compressed_jpegr_image == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
@@ -788,23 +868,23 @@
     primary_image->length = image_ranges[0].GetLength();
   }
 
-  if (recovery_map != nullptr) {
-    recovery_map->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
+  if (gain_map != nullptr) {
+    gain_map->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
                                               image_ranges[1].GetBegin();
-    recovery_map->length = image_ranges[1].GetLength();
+    gain_map->length = image_ranges[1].GetLength();
   }
 
   return NO_ERROR;
 }
 
 
-status_t JpegR::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
-                                   jr_compressed_ptr dest) {
+status_t JpegR::extractGainMap(jr_compressed_ptr compressed_jpegr_image,
+                               jr_compressed_ptr dest) {
   if (compressed_jpegr_image == nullptr || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
-  return extractPrimaryImageAndRecoveryMap(compressed_jpegr_image, nullptr, dest);
+  return extractPrimaryImageAndGainMap(compressed_jpegr_image, nullptr, dest);
 }
 
 // JPEG/R structure:
@@ -833,20 +913,20 @@
 // name space ("http://ns.adobe.com/xap/1.0/\0")
 // XMP
 //
-// (Required) secondary image (the recovery map, without the first two bytes (SOI))
+// (Required) secondary image (the gain map, without the first two bytes (SOI))
 //
 // Metadata versions we are using:
 // ECMA TR-98 for JFIF marker
 // Exif 2.2 spec for EXIF marker
 // Adobe XMP spec part 3 for XMP marker
 // ICC v4.3 spec for ICC
-status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
-                                  jr_compressed_ptr compressed_recovery_map,
-                                  jr_exif_ptr exif,
-                                  jr_metadata_ptr metadata,
-                                  jr_compressed_ptr dest) {
+status_t JpegR::appendGainMap(jr_compressed_ptr compressed_jpeg_image,
+                              jr_compressed_ptr compressed_gain_map,
+                              jr_exif_ptr exif,
+                              ultrahdr_metadata_ptr metadata,
+                              jr_compressed_ptr dest) {
   if (compressed_jpeg_image == nullptr
-   || compressed_recovery_map == nullptr
+   || compressed_gain_map == nullptr
    || metadata == nullptr
    || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
@@ -863,9 +943,9 @@
                                  + xmp_secondary.size(); /* length of xmp packet */
   const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */
                                  + xmp_secondary_length
-                                 + compressed_recovery_map->length;
+                                 + compressed_gain_map->length;
   // primary image
-  const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size);
+  const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata);
   // same as primary
   const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size();
 
@@ -927,7 +1007,7 @@
       (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
   // Finish primary image
 
-  // Begin secondary image (recovery map)
+  // Begin secondary image (gain map)
   // Write SOI
   JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
   JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
@@ -947,7 +1027,7 @@
 
   // Write secondary image
   JPEGR_CHECK(Write(dest,
-        (uint8_t*)compressed_recovery_map->data + 2, compressed_recovery_map->length - 2, pos));
+        (uint8_t*)compressed_gain_map->data + 2, compressed_gain_map->length - 2, pos));
 
   // Set back length
   dest->length = pos;
@@ -961,25 +1041,43 @@
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
+  size_t src_luma_stride = src->luma_stride;
+  size_t src_chroma_stride = src->chroma_stride;
+  uint16_t* src_luma_data = reinterpret_cast<uint16_t*>(src->data);
+  uint16_t* src_chroma_data = reinterpret_cast<uint16_t*>(src->chroma_data);
+
+  if (src_chroma_data == nullptr) {
+    src_chroma_data = &reinterpret_cast<uint16_t*>(src->data)[src_luma_stride * src->height];
+  }
+  if (src_luma_stride == 0) {
+    src_luma_stride = src->width;
+  }
+  if (src_chroma_stride == 0) {
+    src_chroma_stride = src_luma_stride;
+  }
+
   dest->width = src->width;
   dest->height = src->height;
 
-  size_t pixel_count = src->width * src->height;
+  size_t dest_luma_pixel_count = dest->width * dest->height;
+
   for (size_t y = 0; y < src->height; ++y) {
     for (size_t x = 0; x < src->width; ++x) {
-      size_t pixel_y_idx = x + y * src->width;
-      size_t pixel_uv_idx = x / 2 + (y / 2) * (src->width / 2);
+      size_t src_y_idx = y * src_luma_stride + x;
+      size_t src_u_idx = (y >> 1) * src_chroma_stride + (x & ~0x1);
+      size_t src_v_idx = src_u_idx + 1;
 
-      uint16_t y_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_y_idx]
-                        >> 6;
-      uint16_t u_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_count + pixel_uv_idx * 2]
-                        >> 6;
-      uint16_t v_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_count + pixel_uv_idx * 2 + 1]
-                        >> 6;
+      uint16_t y_uint = src_luma_data[src_y_idx] >> 6;
+      uint16_t u_uint = src_chroma_data[src_u_idx] >> 6;
+      uint16_t v_uint = src_chroma_data[src_v_idx] >> 6;
 
-      uint8_t* y = &reinterpret_cast<uint8_t*>(dest->data)[pixel_y_idx];
-      uint8_t* u = &reinterpret_cast<uint8_t*>(dest->data)[pixel_count + pixel_uv_idx];
-      uint8_t* v = &reinterpret_cast<uint8_t*>(dest->data)[pixel_count * 5 / 4 + pixel_uv_idx];
+      size_t dest_y_idx = x + y * dest->width;
+      size_t dest_uv_idx = x / 2 + (y / 2) * (dest->width / 2);
+
+      uint8_t* y = &reinterpret_cast<uint8_t*>(dest->data)[dest_y_idx];
+      uint8_t* u = &reinterpret_cast<uint8_t*>(dest->data)[dest_luma_pixel_count + dest_uv_idx];
+      uint8_t* v = &reinterpret_cast<uint8_t*>(
+              dest->data)[dest_luma_pixel_count * 5 / 4 + dest_uv_idx];
 
       *y = static_cast<uint8_t>((y_uint >> 2) & 0xff);
       *u = static_cast<uint8_t>((u_uint >> 2) & 0xff);
@@ -992,4 +1090,4 @@
   return NO_ERROR;
 }
 
-} // namespace android::jpegrecoverymap
+} // namespace android::ultrahdr
diff --git a/libs/jpegrecoverymap/jpegrutils.cpp b/libs/ultrahdr/jpegrutils.cpp
similarity index 81%
rename from libs/jpegrecoverymap/jpegrutils.cpp
rename to libs/ultrahdr/jpegrutils.cpp
index 38b78ad..6430af1 100644
--- a/libs/jpegrecoverymap/jpegrutils.cpp
+++ b/libs/ultrahdr/jpegrutils.cpp
@@ -14,20 +14,23 @@
  * limitations under the License.
  */
 
-#include <jpegrecoverymap/jpegrutils.h>
-#include <utils/Log.h>
+#include <ultrahdr/jpegrutils.h>
+
+#include <algorithm>
+#include <cmath>
+
 #include <image_io/xml/xml_reader.h>
 #include <image_io/xml/xml_writer.h>
 #include <image_io/base/message_handler.h>
 #include <image_io/xml/xml_element_rules.h>
 #include <image_io/xml/xml_handler.h>
 #include <image_io/xml/xml_rule.h>
-#include <cmath>
+#include <utils/Log.h>
 
 using namespace photos_editing_formats::image_io;
 using namespace std;
 
-namespace android::jpegrecoverymap {
+namespace android::ultrahdr {
 /*
  * Helper function used for generating XMP metadata.
  *
@@ -230,30 +233,30 @@
 const string kItemSemantic         = Name(kItemPrefix, "Semantic");
 
 // Item XMP constants - element and attribute values
-const string kSemanticPrimary     = "Primary";
-const string kSemanticRecoveryMap = "RecoveryMap";
-const string kMimeImageJpeg       = "image/jpeg";
+const string kSemanticPrimary = "Primary";
+const string kSemanticGainMap = "GainMap";
+const string kMimeImageJpeg   = "image/jpeg";
 
-// RecoveryMap XMP constants - URI and namespace prefix
-const string kRecoveryMapUri      = "http://ns.adobe.com/hdr-gain-map/1.0/";
-const string kRecoveryMapPrefix   = "hdrgm";
+// GainMap XMP constants - URI and namespace prefix
+const string kGainMapUri      = "http://ns.adobe.com/hdr-gain-map/1.0/";
+const string kGainMapPrefix   = "hdrgm";
 
-// RecoveryMap XMP constants - element and attribute names
-const string kMapVersion          = Name(kRecoveryMapPrefix, "Version");
-const string kMapGainMapMin       = Name(kRecoveryMapPrefix, "GainMapMin");
-const string kMapGainMapMax       = Name(kRecoveryMapPrefix, "GainMapMax");
-const string kMapGamma            = Name(kRecoveryMapPrefix, "Gamma");
-const string kMapOffsetSdr        = Name(kRecoveryMapPrefix, "OffsetSDR");
-const string kMapOffsetHdr        = Name(kRecoveryMapPrefix, "OffsetHDR");
-const string kMapHDRCapacityMin   = Name(kRecoveryMapPrefix, "HDRCapacityMin");
-const string kMapHDRCapacityMax   = Name(kRecoveryMapPrefix, "HDRCapacityMax");
-const string kMapBaseRendition    = Name(kRecoveryMapPrefix, "BaseRendition");
+// GainMap XMP constants - element and attribute names
+const string kMapVersion            = Name(kGainMapPrefix, "Version");
+const string kMapGainMapMin         = Name(kGainMapPrefix, "GainMapMin");
+const string kMapGainMapMax         = Name(kGainMapPrefix, "GainMapMax");
+const string kMapGamma              = Name(kGainMapPrefix, "Gamma");
+const string kMapOffsetSdr          = Name(kGainMapPrefix, "OffsetSDR");
+const string kMapOffsetHdr          = Name(kGainMapPrefix, "OffsetHDR");
+const string kMapHDRCapacityMin     = Name(kGainMapPrefix, "HDRCapacityMin");
+const string kMapHDRCapacityMax     = Name(kGainMapPrefix, "HDRCapacityMax");
+const string kMapBaseRenditionIsHDR = Name(kGainMapPrefix, "BaseRenditionIsHDR");
 
-// RecoveryMap XMP constants - names for XMP handlers
+// GainMap XMP constants - names for XMP handlers
 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, ultrahdr_metadata_struct* metadata) {
     string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
 
     if (xmp_size < nameSpace.size()+2) {
@@ -299,7 +302,7 @@
     return true;
 }
 
-string generateXmpForPrimaryImage(int secondary_image_length) {
+string generateXmpForPrimaryImage(int secondary_image_length, ultrahdr_metadata_struct& metadata) {
   const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
   const vector<string> kLiItem({string("rdf:li"), kConItem});
 
@@ -313,23 +316,32 @@
   writer.StartWritingElement("rdf:Description");
   writer.WriteXmlns(kContainerPrefix, kContainerUri);
   writer.WriteXmlns(kItemPrefix, kItemUri);
+  writer.WriteXmlns(kGainMapPrefix, kGainMapUri);
+  writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
+
   writer.StartWritingElements(kConDirSeq);
-  size_t item_depth = writer.StartWritingElements(kLiItem);
+
+  size_t item_depth = writer.StartWritingElement("rdf:li");
+  writer.WriteAttributeNameAndValue("rdf:parseType", "Resource");
+  writer.StartWritingElement(kConItem);
   writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary);
   writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg);
   writer.FinishWritingElementsToDepth(item_depth);
-  writer.StartWritingElements(kLiItem);
-  writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticRecoveryMap);
+
+  writer.StartWritingElement("rdf:li");
+  writer.WriteAttributeNameAndValue("rdf:parseType", "Resource");
+  writer.StartWritingElement(kConItem);
+  writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticGainMap);
   writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg);
   writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
+
   writer.FinishWriting();
 
   return ss.str();
 }
 
-string generateXmpForSecondaryImage(jpegr_metadata& metadata) {
+string generateXmpForSecondaryImage(ultrahdr_metadata_struct& metadata) {
   const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
-  const vector<string> kLiItem({string("rdf:li"), kConItem});
 
   std::stringstream ss;
   photos_editing_formats::image_io::XmlWriter writer(ss);
@@ -339,19 +351,20 @@
   writer.StartWritingElement("rdf:RDF");
   writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
   writer.StartWritingElement("rdf:Description");
-  writer.WriteXmlns(kRecoveryMapPrefix, kRecoveryMapUri);
+  writer.WriteXmlns(kGainMapPrefix, kGainMapUri);
   writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
   writer.WriteAttributeNameAndValue(kMapGainMapMin, log2(metadata.minContentBoost));
   writer.WriteAttributeNameAndValue(kMapGainMapMax, log2(metadata.maxContentBoost));
   writer.WriteAttributeNameAndValue(kMapGamma, "1");
   writer.WriteAttributeNameAndValue(kMapOffsetSdr, "0");
   writer.WriteAttributeNameAndValue(kMapOffsetHdr, "0");
-  writer.WriteAttributeNameAndValue(kMapHDRCapacityMin, "0");
-  writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, "2.3");
-  writer.WriteAttributeNameAndValue(kMapBaseRendition, "SDR");
+  writer.WriteAttributeNameAndValue(
+      kMapHDRCapacityMin, std::max(log2(metadata.minContentBoost), 0.0f));
+  writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, log2(metadata.maxContentBoost));
+  writer.WriteAttributeNameAndValue(kMapBaseRenditionIsHDR, "False");
   writer.FinishWriting();
 
   return ss.str();
 }
 
-} // namespace android::jpegrecoverymap
+} // namespace android::ultrahdr
diff --git a/libs/jpegrecoverymap/multipictureformat.cpp b/libs/ultrahdr/multipictureformat.cpp
similarity index 95%
rename from libs/jpegrecoverymap/multipictureformat.cpp
rename to libs/ultrahdr/multipictureformat.cpp
index a219aef..7a265c6 100644
--- a/libs/jpegrecoverymap/multipictureformat.cpp
+++ b/libs/ultrahdr/multipictureformat.cpp
@@ -13,10 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#include <jpegrecoverymap/multipictureformat.h>
-#include <jpegrecoverymap/jpegrutils.h>
+#include <ultrahdr/multipictureformat.h>
+#include <ultrahdr/jpegrutils.h>
 
-namespace android::jpegrecoverymap {
+namespace android::ultrahdr {
 size_t calculateMpfSize() {
     return sizeof(kMpfSig) +                 // Signature
             kMpEndianSize +                   // Endianness
@@ -91,4 +91,4 @@
     return dataStruct;
 }
 
-} // namespace android::jpegrecoverymap
+} // namespace android::ultrahdr
diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/ultrahdr/tests/Android.bp
similarity index 93%
rename from libs/jpegrecoverymap/tests/Android.bp
rename to libs/ultrahdr/tests/Android.bp
index 61b3db9..7dd9d04 100644
--- a/libs/jpegrecoverymap/tests/Android.bp
+++ b/libs/ultrahdr/tests/Android.bp
@@ -22,11 +22,11 @@
 }
 
 cc_test {
-    name: "libjpegrecoverymap_test",
+    name: "libultrahdr_test",
     test_suites: ["device-tests"],
     srcs: [
         "jpegr_test.cpp",
-        "recoverymapmath_test.cpp",
+        "gainmapmath_test.cpp",
     ],
     shared_libs: [
         "libimage_io",
@@ -38,8 +38,7 @@
         "libgtest",
         "libjpegdecoder",
         "libjpegencoder",
-        "libjpegrecoverymap",
-        "libskia",
+        "libultrahdr",
         "libutils",
     ],
 }
diff --git a/libs/jpegrecoverymap/tests/data/jpeg_image.jpg b/libs/ultrahdr/tests/data/jpeg_image.jpg
similarity index 100%
rename from libs/jpegrecoverymap/tests/data/jpeg_image.jpg
rename to libs/ultrahdr/tests/data/jpeg_image.jpg
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/data/minnie-318x240.yu12 b/libs/ultrahdr/tests/data/minnie-318x240.yu12
similarity index 100%
rename from libs/jpegrecoverymap/tests/data/minnie-318x240.yu12
rename to libs/ultrahdr/tests/data/minnie-318x240.yu12
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240-y.jpg b/libs/ultrahdr/tests/data/minnie-320x240-y.jpg
similarity index 100%
rename from libs/jpegrecoverymap/tests/data/minnie-320x240-y.jpg
rename to libs/ultrahdr/tests/data/minnie-320x240-y.jpg
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240-yuv.jpg b/libs/ultrahdr/tests/data/minnie-320x240-yuv.jpg
similarity index 100%
rename from libs/jpegrecoverymap/tests/data/minnie-320x240-yuv.jpg
rename to libs/ultrahdr/tests/data/minnie-320x240-yuv.jpg
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240.y b/libs/ultrahdr/tests/data/minnie-320x240.y
similarity index 100%
rename from libs/jpegrecoverymap/tests/data/minnie-320x240.y
rename to libs/ultrahdr/tests/data/minnie-320x240.y
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240.yu12 b/libs/ultrahdr/tests/data/minnie-320x240.yu12
similarity index 100%
rename from libs/jpegrecoverymap/tests/data/minnie-320x240.yu12
rename to libs/ultrahdr/tests/data/minnie-320x240.yu12
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/data/raw_p010_image.p010 b/libs/ultrahdr/tests/data/raw_p010_image.p010
similarity index 100%
rename from libs/jpegrecoverymap/tests/data/raw_p010_image.p010
rename to libs/ultrahdr/tests/data/raw_p010_image.p010
Binary files differ
diff --git a/libs/ultrahdr/tests/data/raw_p010_image_with_stride.p010 b/libs/ultrahdr/tests/data/raw_p010_image_with_stride.p010
new file mode 100644
index 0000000..e7a5dc8
--- /dev/null
+++ b/libs/ultrahdr/tests/data/raw_p010_image_with_stride.p010
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/data/raw_yuv420_image.yuv420 b/libs/ultrahdr/tests/data/raw_yuv420_image.yuv420
similarity index 100%
rename from libs/jpegrecoverymap/tests/data/raw_yuv420_image.yuv420
rename to libs/ultrahdr/tests/data/raw_yuv420_image.yuv420
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp b/libs/ultrahdr/tests/gainmapmath_test.cpp
similarity index 65%
rename from libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
rename to libs/ultrahdr/tests/gainmapmath_test.cpp
index 6c61ff1..c456653 100644
--- a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
+++ b/libs/ultrahdr/tests/gainmapmath_test.cpp
@@ -17,14 +17,14 @@
 #include <cmath>
 #include <gtest/gtest.h>
 #include <gmock/gmock.h>
-#include <jpegrecoverymap/recoverymapmath.h>
+#include <ultrahdr/gainmapmath.h>
 
-namespace android::jpegrecoverymap {
+namespace android::ultrahdr {
 
-class RecoveryMapMathTest : public testing::Test {
+class GainMapMathTest : public testing::Test {
 public:
-  RecoveryMapMathTest();
-  ~RecoveryMapMathTest();
+  GainMapMathTest();
+  ~GainMapMathTest();
 
   float ComparisonEpsilon() { return 1e-4f; }
   float LuminanceEpsilon() { return 1e-2f; }
@@ -88,10 +88,10 @@
     return luminance_scaled * scale_factor;
   }
 
-  Color Recover(Color yuv_gamma, float recovery, jr_metadata_ptr metadata) {
+  Color Recover(Color yuv_gamma, float gain, ultrahdr_metadata_ptr metadata) {
     Color rgb_gamma = srgbYuvToRgb(yuv_gamma);
     Color rgb = srgbInvOetf(rgb_gamma);
-    return applyRecovery(rgb, recovery, metadata);
+    return applyGain(rgb, gain, metadata);
   }
 
   jpegr_uncompressed_struct Yuv420Image() {
@@ -108,7 +108,7 @@
       0xB0, 0xB1,
       0xB2, 0xB3,
     };
-    return { pixels, 4, 4, JPEGR_COLORGAMUT_BT709 };
+    return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709 };
   }
 
   Color (*Yuv420Colors())[4] {
@@ -141,7 +141,7 @@
       0xA0 << 6, 0xB0 << 6, 0xA1 << 6, 0xB1 << 6,
       0xA2 << 6, 0xB2 << 6, 0xA3 << 6, 0xB3 << 6,
     };
-    return { pixels, 4, 4, JPEGR_COLORGAMUT_BT709 };
+    return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709 };
   }
 
   Color (*P010Colors())[4] {
@@ -170,7 +170,7 @@
       0x02, 0x12, 0x22, 0x32,
       0x03, 0x13, 0x23, 0x33,
     };
-    return { pixels, 4, 4, JPEGR_COLORGAMUT_UNSPECIFIED };
+    return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_UNSPECIFIED };
   }
 
   float (*MapValues())[4] {
@@ -193,11 +193,11 @@
   virtual void TearDown();
 };
 
-RecoveryMapMathTest::RecoveryMapMathTest() {}
-RecoveryMapMathTest::~RecoveryMapMathTest() {}
+GainMapMathTest::GainMapMathTest() {}
+GainMapMathTest::~GainMapMathTest() {}
 
-void RecoveryMapMathTest::SetUp() {}
-void RecoveryMapMathTest::TearDown() {}
+void GainMapMathTest::SetUp() {}
+void GainMapMathTest::TearDown() {}
 
 #define EXPECT_RGB_EQ(e1, e2)       \
   EXPECT_FLOAT_EQ((e1).r, (e2).r);  \
@@ -231,7 +231,7 @@
 
 // TODO: a bunch of these tests can be parameterized.
 
-TEST_F(RecoveryMapMathTest, ColorConstruct) {
+TEST_F(GainMapMathTest, ColorConstruct) {
   Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
 
   EXPECT_FLOAT_EQ(e1.r, 0.1f);
@@ -243,7 +243,7 @@
   EXPECT_FLOAT_EQ(e1.v, 0.3f);
 }
 
-TEST_F(RecoveryMapMathTest, ColorAddColor) {
+TEST_F(GainMapMathTest, ColorAddColor) {
   Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
 
   Color e2 = e1 + e1;
@@ -257,7 +257,7 @@
   EXPECT_FLOAT_EQ(e2.b, e1.b * 3.0f);
 }
 
-TEST_F(RecoveryMapMathTest, ColorAddFloat) {
+TEST_F(GainMapMathTest, ColorAddFloat) {
   Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
 
   Color e2 = e1 + 0.1f;
@@ -271,7 +271,7 @@
   EXPECT_FLOAT_EQ(e2.b, e1.b + 0.2f);
 }
 
-TEST_F(RecoveryMapMathTest, ColorSubtractColor) {
+TEST_F(GainMapMathTest, ColorSubtractColor) {
   Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
 
   Color e2 = e1 - e1;
@@ -285,7 +285,7 @@
   EXPECT_FLOAT_EQ(e2.b, -e1.b);
 }
 
-TEST_F(RecoveryMapMathTest, ColorSubtractFloat) {
+TEST_F(GainMapMathTest, ColorSubtractFloat) {
   Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
 
   Color e2 = e1 - 0.1f;
@@ -299,7 +299,7 @@
   EXPECT_FLOAT_EQ(e2.b, e1.b - 0.2f);
 }
 
-TEST_F(RecoveryMapMathTest, ColorMultiplyFloat) {
+TEST_F(GainMapMathTest, ColorMultiplyFloat) {
   Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
 
   Color e2 = e1 * 2.0f;
@@ -313,7 +313,7 @@
   EXPECT_FLOAT_EQ(e2.b, e1.b * 4.0f);
 }
 
-TEST_F(RecoveryMapMathTest, ColorDivideFloat) {
+TEST_F(GainMapMathTest, ColorDivideFloat) {
   Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
 
   Color e2 = e1 / 2.0f;
@@ -327,7 +327,7 @@
   EXPECT_FLOAT_EQ(e2.b, e1.b / 4.0f);
 }
 
-TEST_F(RecoveryMapMathTest, SrgbLuminance) {
+TEST_F(GainMapMathTest, SrgbLuminance) {
   EXPECT_FLOAT_EQ(srgbLuminance(RgbBlack()), 0.0f);
   EXPECT_FLOAT_EQ(srgbLuminance(RgbWhite()), 1.0f);
   EXPECT_FLOAT_EQ(srgbLuminance(RgbRed()), 0.2126f);
@@ -335,7 +335,7 @@
   EXPECT_FLOAT_EQ(srgbLuminance(RgbBlue()), 0.0722f);
 }
 
-TEST_F(RecoveryMapMathTest, SrgbYuvToRgb) {
+TEST_F(GainMapMathTest, SrgbYuvToRgb) {
   Color rgb_black = srgbYuvToRgb(YuvBlack());
   EXPECT_RGB_NEAR(rgb_black, RgbBlack());
 
@@ -352,7 +352,7 @@
   EXPECT_RGB_NEAR(rgb_b, RgbBlue());
 }
 
-TEST_F(RecoveryMapMathTest, SrgbRgbToYuv) {
+TEST_F(GainMapMathTest, SrgbRgbToYuv) {
   Color yuv_black = srgbRgbToYuv(RgbBlack());
   EXPECT_YUV_NEAR(yuv_black, YuvBlack());
 
@@ -369,7 +369,7 @@
   EXPECT_YUV_NEAR(yuv_b, SrgbYuvBlue());
 }
 
-TEST_F(RecoveryMapMathTest, SrgbRgbYuvRoundtrip) {
+TEST_F(GainMapMathTest, SrgbRgbYuvRoundtrip) {
   Color rgb_black = srgbYuvToRgb(srgbRgbToYuv(RgbBlack()));
   EXPECT_RGB_NEAR(rgb_black, RgbBlack());
 
@@ -386,7 +386,7 @@
   EXPECT_RGB_NEAR(rgb_b, RgbBlue());
 }
 
-TEST_F(RecoveryMapMathTest, SrgbTransferFunction) {
+TEST_F(GainMapMathTest, SrgbTransferFunction) {
   EXPECT_FLOAT_EQ(srgbInvOetf(0.0f), 0.0f);
   EXPECT_NEAR(srgbInvOetf(0.02f), 0.00154f, ComparisonEpsilon());
   EXPECT_NEAR(srgbInvOetf(0.04045f), 0.00313f, ComparisonEpsilon());
@@ -394,7 +394,7 @@
   EXPECT_FLOAT_EQ(srgbInvOetf(1.0f), 1.0f);
 }
 
-TEST_F(RecoveryMapMathTest, P3Luminance) {
+TEST_F(GainMapMathTest, P3Luminance) {
   EXPECT_FLOAT_EQ(p3Luminance(RgbBlack()), 0.0f);
   EXPECT_FLOAT_EQ(p3Luminance(RgbWhite()), 1.0f);
   EXPECT_FLOAT_EQ(p3Luminance(RgbRed()), 0.20949f);
@@ -402,7 +402,7 @@
   EXPECT_FLOAT_EQ(p3Luminance(RgbBlue()), 0.06891f);
 }
 
-TEST_F(RecoveryMapMathTest, Bt2100Luminance) {
+TEST_F(GainMapMathTest, Bt2100Luminance) {
   EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlack()), 0.0f);
   EXPECT_FLOAT_EQ(bt2100Luminance(RgbWhite()), 1.0f);
   EXPECT_FLOAT_EQ(bt2100Luminance(RgbRed()), 0.2627f);
@@ -410,7 +410,7 @@
   EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlue()), 0.0593f);
 }
 
-TEST_F(RecoveryMapMathTest, Bt2100YuvToRgb) {
+TEST_F(GainMapMathTest, Bt2100YuvToRgb) {
   Color rgb_black = bt2100YuvToRgb(YuvBlack());
   EXPECT_RGB_NEAR(rgb_black, RgbBlack());
 
@@ -427,7 +427,7 @@
   EXPECT_RGB_NEAR(rgb_b, RgbBlue());
 }
 
-TEST_F(RecoveryMapMathTest, Bt2100RgbToYuv) {
+TEST_F(GainMapMathTest, Bt2100RgbToYuv) {
   Color yuv_black = bt2100RgbToYuv(RgbBlack());
   EXPECT_YUV_NEAR(yuv_black, YuvBlack());
 
@@ -444,7 +444,7 @@
   EXPECT_YUV_NEAR(yuv_b, Bt2100YuvBlue());
 }
 
-TEST_F(RecoveryMapMathTest, Bt2100RgbYuvRoundtrip) {
+TEST_F(GainMapMathTest, Bt2100RgbYuvRoundtrip) {
   Color rgb_black = bt2100YuvToRgb(bt2100RgbToYuv(RgbBlack()));
   EXPECT_RGB_NEAR(rgb_black, RgbBlack());
 
@@ -461,7 +461,7 @@
   EXPECT_RGB_NEAR(rgb_b, RgbBlue());
 }
 
-TEST_F(RecoveryMapMathTest, HlgOetf) {
+TEST_F(GainMapMathTest, HlgOetf) {
   EXPECT_FLOAT_EQ(hlgOetf(0.0f), 0.0f);
   EXPECT_NEAR(hlgOetf(0.04167f), 0.35357f, ComparisonEpsilon());
   EXPECT_NEAR(hlgOetf(0.08333f), 0.5f, ComparisonEpsilon());
@@ -473,7 +473,7 @@
   EXPECT_RGB_NEAR(hlgOetf(e), e_gamma);
 }
 
-TEST_F(RecoveryMapMathTest, HlgInvOetf) {
+TEST_F(GainMapMathTest, HlgInvOetf) {
   EXPECT_FLOAT_EQ(hlgInvOetf(0.0f), 0.0f);
   EXPECT_NEAR(hlgInvOetf(0.25f), 0.02083f, ComparisonEpsilon());
   EXPECT_NEAR(hlgInvOetf(0.5f), 0.08333f, ComparisonEpsilon());
@@ -485,7 +485,7 @@
   EXPECT_RGB_NEAR(hlgInvOetf(e_gamma), e);
 }
 
-TEST_F(RecoveryMapMathTest, HlgTransferFunctionRoundtrip) {
+TEST_F(GainMapMathTest, HlgTransferFunctionRoundtrip) {
   EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(0.0f)), 0.0f);
   EXPECT_NEAR(hlgInvOetf(hlgOetf(0.04167f)), 0.04167f, ComparisonEpsilon());
   EXPECT_NEAR(hlgInvOetf(hlgOetf(0.08333f)), 0.08333f, ComparisonEpsilon());
@@ -493,7 +493,7 @@
   EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(1.0f)), 1.0f);
 }
 
-TEST_F(RecoveryMapMathTest, PqOetf) {
+TEST_F(GainMapMathTest, PqOetf) {
   EXPECT_FLOAT_EQ(pqOetf(0.0f), 0.0f);
   EXPECT_NEAR(pqOetf(0.01f), 0.50808f, ComparisonEpsilon());
   EXPECT_NEAR(pqOetf(0.5f), 0.92655f, ComparisonEpsilon());
@@ -505,7 +505,7 @@
   EXPECT_RGB_NEAR(pqOetf(e), e_gamma);
 }
 
-TEST_F(RecoveryMapMathTest, PqInvOetf) {
+TEST_F(GainMapMathTest, PqInvOetf) {
   EXPECT_FLOAT_EQ(pqInvOetf(0.0f), 0.0f);
   EXPECT_NEAR(pqInvOetf(0.01f), 2.31017e-7f, ComparisonEpsilon());
   EXPECT_NEAR(pqInvOetf(0.5f), 0.00922f, ComparisonEpsilon());
@@ -517,102 +517,135 @@
   EXPECT_RGB_NEAR(pqInvOetf(e_gamma), e);
 }
 
-TEST_F(RecoveryMapMathTest, PqInvOetfLUT) {
+TEST_F(GainMapMathTest, PqInvOetfLUT) {
     for (int idx = 0; idx < kPqInvOETFNumEntries; idx++) {
       float value = static_cast<float>(idx) / static_cast<float>(kPqInvOETFNumEntries - 1);
       EXPECT_FLOAT_EQ(pqInvOetf(value), pqInvOetfLUT(value));
     }
 }
 
-TEST_F(RecoveryMapMathTest, HlgInvOetfLUT) {
+TEST_F(GainMapMathTest, HlgInvOetfLUT) {
     for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++) {
       float value = static_cast<float>(idx) / static_cast<float>(kHlgInvOETFNumEntries - 1);
       EXPECT_FLOAT_EQ(hlgInvOetf(value), hlgInvOetfLUT(value));
     }
 }
 
-TEST_F(RecoveryMapMathTest, pqOetfLUT) {
+TEST_F(GainMapMathTest, pqOetfLUT) {
     for (int idx = 0; idx < kPqOETFNumEntries; idx++) {
       float value = static_cast<float>(idx) / static_cast<float>(kPqOETFNumEntries - 1);
       EXPECT_FLOAT_EQ(pqOetf(value), pqOetfLUT(value));
     }
 }
 
-TEST_F(RecoveryMapMathTest, hlgOetfLUT) {
+TEST_F(GainMapMathTest, hlgOetfLUT) {
     for (int idx = 0; idx < kHlgOETFNumEntries; idx++) {
       float value = static_cast<float>(idx) / static_cast<float>(kHlgOETFNumEntries - 1);
       EXPECT_FLOAT_EQ(hlgOetf(value), hlgOetfLUT(value));
     }
 }
 
-TEST_F(RecoveryMapMathTest, srgbInvOetfLUT) {
+TEST_F(GainMapMathTest, srgbInvOetfLUT) {
     for (int idx = 0; idx < kSrgbInvOETFNumEntries; idx++) {
       float value = static_cast<float>(idx) / static_cast<float>(kSrgbInvOETFNumEntries - 1);
       EXPECT_FLOAT_EQ(srgbInvOetf(value), srgbInvOetfLUT(value));
     }
 }
 
-TEST_F(RecoveryMapMathTest, applyRecoveryLUT) {
+TEST_F(GainMapMathTest, applyGainLUT) {
   for (int boost = 1; boost <= 10; boost++) {
-    jpegr_metadata 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);
-      EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, &metadata),
-                      applyRecoveryLUT(RgbBlack(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, &metadata),
-                      applyRecoveryLUT(RgbWhite(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, &metadata),
-                      applyRecoveryLUT(RgbRed(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, &metadata),
-                      applyRecoveryLUT(RgbGreen(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, &metadata),
-                      applyRecoveryLUT(RgbBlue(), value, recoveryLUT));
+    ultrahdr_metadata_struct metadata = { .maxContentBoost = static_cast<float>(boost),
+                                       .minContentBoost = 1.0f / static_cast<float>(boost) };
+    GainLUT gainLUT(&metadata);
+    GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost);
+    for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
+      float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
+      EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata),
+                      applyGainLUT(RgbBlack(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata),
+                      applyGainLUT(RgbWhite(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata),
+                      applyGainLUT(RgbRed(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata),
+                      applyGainLUT(RgbGreen(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata),
+                      applyGainLUT(RgbBlue(), value, gainLUT));
+      EXPECT_RGB_EQ(applyGainLUT(RgbBlack(), value, gainLUT),
+                    applyGainLUT(RgbBlack(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbWhite(), value, gainLUT),
+                    applyGainLUT(RgbWhite(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbRed(), value, gainLUT),
+                    applyGainLUT(RgbRed(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbGreen(), value, gainLUT),
+                    applyGainLUT(RgbGreen(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbBlue(), value, gainLUT),
+                    applyGainLUT(RgbBlue(), value, gainLUTWithBoost));
     }
   }
 
   for (int boost = 1; boost <= 10; boost++) {
-    jpegr_metadata 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);
-      EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, &metadata),
-                      applyRecoveryLUT(RgbBlack(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, &metadata),
-                      applyRecoveryLUT(RgbWhite(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, &metadata),
-                      applyRecoveryLUT(RgbRed(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, &metadata),
-                      applyRecoveryLUT(RgbGreen(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, &metadata),
-                      applyRecoveryLUT(RgbBlue(), value, recoveryLUT));
+    ultrahdr_metadata_struct metadata = { .maxContentBoost = static_cast<float>(boost),
+                                       .minContentBoost = 1.0f };
+    GainLUT gainLUT(&metadata);
+    GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost);
+    for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
+      float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
+      EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata),
+                      applyGainLUT(RgbBlack(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata),
+                      applyGainLUT(RgbWhite(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata),
+                      applyGainLUT(RgbRed(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata),
+                      applyGainLUT(RgbGreen(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata),
+                      applyGainLUT(RgbBlue(), value, gainLUT));
+      EXPECT_RGB_EQ(applyGainLUT(RgbBlack(), value, gainLUT),
+                    applyGainLUT(RgbBlack(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbWhite(), value, gainLUT),
+                    applyGainLUT(RgbWhite(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbRed(), value, gainLUT),
+                    applyGainLUT(RgbRed(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbGreen(), value, gainLUT),
+                    applyGainLUT(RgbGreen(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbBlue(), value, gainLUT),
+                    applyGainLUT(RgbBlue(), value, gainLUTWithBoost));
     }
   }
 
   for (int boost = 1; boost <= 10; boost++) {
-    jpegr_metadata metadata = { .maxContentBoost = static_cast<float>(boost),
-                                .minContentBoost = 1.0f / pow(static_cast<float>(boost),
+    ultrahdr_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++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kRecoveryFactorNumEntries - 1);
-      EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, &metadata),
-                      applyRecoveryLUT(RgbBlack(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, &metadata),
-                      applyRecoveryLUT(RgbWhite(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, &metadata),
-                      applyRecoveryLUT(RgbRed(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, &metadata),
-                      applyRecoveryLUT(RgbGreen(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, &metadata),
-                      applyRecoveryLUT(RgbBlue(), value, recoveryLUT));
+    GainLUT gainLUT(&metadata);
+    GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost);
+    for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
+      float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
+      EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata),
+                      applyGainLUT(RgbBlack(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata),
+                      applyGainLUT(RgbWhite(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata),
+                      applyGainLUT(RgbRed(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata),
+                      applyGainLUT(RgbGreen(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata),
+                      applyGainLUT(RgbBlue(), value, gainLUT));
+      EXPECT_RGB_EQ(applyGainLUT(RgbBlack(), value, gainLUT),
+                    applyGainLUT(RgbBlack(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbWhite(), value, gainLUT),
+                    applyGainLUT(RgbWhite(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbRed(), value, gainLUT),
+                    applyGainLUT(RgbRed(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbGreen(), value, gainLUT),
+                    applyGainLUT(RgbGreen(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbBlue(), value, gainLUT),
+                    applyGainLUT(RgbBlue(), value, gainLUTWithBoost));
     }
   }
 }
 
-TEST_F(RecoveryMapMathTest, PqTransferFunctionRoundtrip) {
+TEST_F(GainMapMathTest, PqTransferFunctionRoundtrip) {
   EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(0.0f)), 0.0f);
   EXPECT_NEAR(pqInvOetf(pqOetf(0.01f)), 0.01f, ComparisonEpsilon());
   EXPECT_NEAR(pqInvOetf(pqOetf(0.5f)), 0.5f, ComparisonEpsilon());
@@ -620,163 +653,177 @@
   EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(1.0f)), 1.0f);
 }
 
-TEST_F(RecoveryMapMathTest, ColorConversionLookup) {
-  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_UNSPECIFIED),
+TEST_F(GainMapMathTest, ColorConversionLookup) {
+  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_UNSPECIFIED),
             nullptr);
-  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_BT709),
+  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_BT709),
             identityConversion);
-  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_P3),
+  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3),
             p3ToBt709);
-  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_BT2100),
+  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_BT2100),
             bt2100ToBt709);
 
-  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_P3, JPEGR_COLORGAMUT_UNSPECIFIED),
+  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_UNSPECIFIED),
             nullptr);
-  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_P3, JPEGR_COLORGAMUT_BT709),
+  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_BT709),
             bt709ToP3);
-  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_P3, JPEGR_COLORGAMUT_P3),
+  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_P3),
             identityConversion);
-  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_P3, JPEGR_COLORGAMUT_BT2100),
+  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_BT2100),
             bt2100ToP3);
 
-  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT2100, JPEGR_COLORGAMUT_UNSPECIFIED),
+  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_UNSPECIFIED),
             nullptr);
-  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT2100, JPEGR_COLORGAMUT_BT709),
+  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_BT709),
             bt709ToBt2100);
-  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT2100, JPEGR_COLORGAMUT_P3),
+  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_P3),
             p3ToBt2100);
-  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT2100, JPEGR_COLORGAMUT_BT2100),
+  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_BT2100),
             identityConversion);
 
-  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_UNSPECIFIED, JPEGR_COLORGAMUT_UNSPECIFIED),
+  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_UNSPECIFIED),
             nullptr);
-  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_UNSPECIFIED, JPEGR_COLORGAMUT_BT709),
+  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_BT709),
             nullptr);
-  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_UNSPECIFIED, JPEGR_COLORGAMUT_P3),
+  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_P3),
             nullptr);
-  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_UNSPECIFIED, JPEGR_COLORGAMUT_BT2100),
+  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_BT2100),
             nullptr);
 }
 
-TEST_F(RecoveryMapMathTest, EncodeRecovery) {
-  jpegr_metadata metadata = { .maxContentBoost = 4.0f,
-                              .minContentBoost = 1.0f / 4.0f };
+TEST_F(GainMapMathTest, EncodeGain) {
+  ultrahdr_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);
-  EXPECT_EQ(encodeRecovery(1.0f, 0.0f, &metadata), 0);
-  EXPECT_EQ(encodeRecovery(0.5f, 0.0f, &metadata), 0);
+  EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 127);
+  EXPECT_EQ(encodeGain(0.0f, 1.0f, &metadata), 127);
+  EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0);
+  EXPECT_EQ(encodeGain(0.5f, 0.0f, &metadata), 0);
 
-  EXPECT_EQ(encodeRecovery(1.0f, 1.0f, &metadata), 127);
-  EXPECT_EQ(encodeRecovery(1.0f, 4.0f, &metadata), 255);
-  EXPECT_EQ(encodeRecovery(1.0f, 5.0f, &metadata), 255);
-  EXPECT_EQ(encodeRecovery(4.0f, 1.0f, &metadata), 0);
-  EXPECT_EQ(encodeRecovery(4.0f, 0.5f, &metadata), 0);
-  EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 191);
-  EXPECT_EQ(encodeRecovery(2.0f, 1.0f, &metadata), 63);
+  EXPECT_EQ(encodeGain(1.0f, 1.0f, &metadata), 127);
+  EXPECT_EQ(encodeGain(1.0f, 4.0f, &metadata), 255);
+  EXPECT_EQ(encodeGain(1.0f, 5.0f, &metadata), 255);
+  EXPECT_EQ(encodeGain(4.0f, 1.0f, &metadata), 0);
+  EXPECT_EQ(encodeGain(4.0f, 0.5f, &metadata), 0);
+  EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 191);
+  EXPECT_EQ(encodeGain(2.0f, 1.0f, &metadata), 63);
 
   metadata.maxContentBoost = 2.0f;
   metadata.minContentBoost = 1.0f / 2.0f;
 
-  EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 255);
-  EXPECT_EQ(encodeRecovery(2.0f, 1.0f, &metadata), 0);
-  EXPECT_EQ(encodeRecovery(1.0f, 1.41421f, &metadata), 191);
-  EXPECT_EQ(encodeRecovery(1.41421f, 1.0f, &metadata), 63);
+  EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 255);
+  EXPECT_EQ(encodeGain(2.0f, 1.0f, &metadata), 0);
+  EXPECT_EQ(encodeGain(1.0f, 1.41421f, &metadata), 191);
+  EXPECT_EQ(encodeGain(1.41421f, 1.0f, &metadata), 63);
 
   metadata.maxContentBoost = 8.0f;
   metadata.minContentBoost = 1.0f / 8.0f;
 
-  EXPECT_EQ(encodeRecovery(1.0f, 8.0f, &metadata), 255);
-  EXPECT_EQ(encodeRecovery(8.0f, 1.0f, &metadata), 0);
-  EXPECT_EQ(encodeRecovery(1.0f, 2.82843f, &metadata), 191);
-  EXPECT_EQ(encodeRecovery(2.82843f, 1.0f, &metadata), 63);
+  EXPECT_EQ(encodeGain(1.0f, 8.0f, &metadata), 255);
+  EXPECT_EQ(encodeGain(8.0f, 1.0f, &metadata), 0);
+  EXPECT_EQ(encodeGain(1.0f, 2.82843f, &metadata), 191);
+  EXPECT_EQ(encodeGain(2.82843f, 1.0f, &metadata), 63);
 
   metadata.maxContentBoost = 8.0f;
   metadata.minContentBoost = 1.0f;
 
-  EXPECT_EQ(encodeRecovery(0.0f, 0.0f, &metadata), 0);
-  EXPECT_EQ(encodeRecovery(1.0f, 0.0f, &metadata), 0);
+  EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 0);
+  EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0);
 
-  EXPECT_EQ(encodeRecovery(1.0f, 1.0f, &metadata), 0);
-  EXPECT_EQ(encodeRecovery(1.0f, 8.0f, &metadata), 255);
-  EXPECT_EQ(encodeRecovery(1.0f, 4.0f, &metadata), 170);
-  EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 85);
+  EXPECT_EQ(encodeGain(1.0f, 1.0f, &metadata), 0);
+  EXPECT_EQ(encodeGain(1.0f, 8.0f, &metadata), 255);
+  EXPECT_EQ(encodeGain(1.0f, 4.0f, &metadata), 170);
+  EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 85);
 
   metadata.maxContentBoost = 8.0f;
   metadata.minContentBoost = 0.5f;
 
-  EXPECT_EQ(encodeRecovery(0.0f, 0.0f, &metadata), 63);
-  EXPECT_EQ(encodeRecovery(1.0f, 0.0f, &metadata), 0);
+  EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 63);
+  EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0);
 
-  EXPECT_EQ(encodeRecovery(1.0f, 1.0f, &metadata), 63);
-  EXPECT_EQ(encodeRecovery(1.0f, 8.0f, &metadata), 255);
-  EXPECT_EQ(encodeRecovery(1.0f, 4.0f, &metadata), 191);
-  EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 127);
-  EXPECT_EQ(encodeRecovery(1.0f, 0.7071f, &metadata), 31);
-  EXPECT_EQ(encodeRecovery(1.0f, 0.5f, &metadata), 0);
+  EXPECT_EQ(encodeGain(1.0f, 1.0f, &metadata), 63);
+  EXPECT_EQ(encodeGain(1.0f, 8.0f, &metadata), 255);
+  EXPECT_EQ(encodeGain(1.0f, 4.0f, &metadata), 191);
+  EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 127);
+  EXPECT_EQ(encodeGain(1.0f, 0.7071f, &metadata), 31);
+  EXPECT_EQ(encodeGain(1.0f, 0.5f, &metadata), 0);
 }
 
-TEST_F(RecoveryMapMathTest, ApplyRecovery) {
-  jpegr_metadata metadata = { .maxContentBoost = 4.0f,
-                              .minContentBoost = 1.0f / 4.0f };
+TEST_F(GainMapMathTest, ApplyGain) {
+  ultrahdr_metadata_struct metadata = { .maxContentBoost = 4.0f,
+                                     .minContentBoost = 1.0f / 4.0f };
+  float displayBoost = metadata.maxContentBoost;
 
-  EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.0f, &metadata), RgbBlack());
-  EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.5f, &metadata), RgbBlack());
-  EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 1.0f, &metadata), RgbBlack());
+  EXPECT_RGB_NEAR(applyGain(RgbBlack(), 0.0f, &metadata), RgbBlack());
+  EXPECT_RGB_NEAR(applyGain(RgbBlack(), 0.5f, &metadata), RgbBlack());
+  EXPECT_RGB_NEAR(applyGain(RgbBlack(), 1.0f, &metadata), RgbBlack());
 
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 4.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite());
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 4.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 4.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite());
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 4.0f);
 
   metadata.maxContentBoost = 2.0f;
   metadata.minContentBoost = 1.0f / 2.0f;
 
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite() / 1.41421f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite());
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 1.41421f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 2.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 1.41421f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite());
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 1.41421f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 2.0f);
 
   metadata.maxContentBoost = 8.0f;
   metadata.minContentBoost = 1.0f / 8.0f;
 
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 8.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.82843f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite());
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.82843f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 8.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.82843f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite());
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.82843f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
 
   metadata.maxContentBoost = 8.0f;
   metadata.minContentBoost = 1.0f;
 
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite());
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f / 3.0f, &metadata), RgbWhite() * 2.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 2.0f / 3.0f, &metadata), RgbWhite() * 4.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite());
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f / 3.0f, &metadata), RgbWhite() * 2.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 2.0f / 3.0f, &metadata), RgbWhite() * 4.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
 
   metadata.maxContentBoost = 8.0f;
   metadata.minContentBoost = 0.5f;
 
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite());
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite() * 2.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 4.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite());
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite() * 2.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 4.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
 
   Color e = {{{ 0.0f, 0.5f, 1.0f }}};
   metadata.maxContentBoost = 4.0f;
   metadata.minContentBoost = 1.0f / 4.0f;
 
-  EXPECT_RGB_NEAR(applyRecovery(e, 0.0f, &metadata), e / 4.0f);
-  EXPECT_RGB_NEAR(applyRecovery(e, 0.25f, &metadata), e / 2.0f);
-  EXPECT_RGB_NEAR(applyRecovery(e, 0.5f, &metadata), e);
-  EXPECT_RGB_NEAR(applyRecovery(e, 0.75f, &metadata), e * 2.0f);
-  EXPECT_RGB_NEAR(applyRecovery(e, 1.0f, &metadata), e * 4.0f);
+  EXPECT_RGB_NEAR(applyGain(e, 0.0f, &metadata), e / 4.0f);
+  EXPECT_RGB_NEAR(applyGain(e, 0.25f, &metadata), e / 2.0f);
+  EXPECT_RGB_NEAR(applyGain(e, 0.5f, &metadata), e);
+  EXPECT_RGB_NEAR(applyGain(e, 0.75f, &metadata), e * 2.0f);
+  EXPECT_RGB_NEAR(applyGain(e, 1.0f, &metadata), e * 4.0f);
+
+  EXPECT_RGB_EQ(applyGain(RgbBlack(), 1.0f, &metadata),
+                applyGain(RgbBlack(), 1.0f, &metadata, displayBoost));
+  EXPECT_RGB_EQ(applyGain(RgbWhite(), 1.0f, &metadata),
+                applyGain(RgbWhite(), 1.0f, &metadata, displayBoost));
+  EXPECT_RGB_EQ(applyGain(RgbRed(), 1.0f, &metadata),
+                applyGain(RgbRed(), 1.0f, &metadata, displayBoost));
+  EXPECT_RGB_EQ(applyGain(RgbGreen(), 1.0f, &metadata),
+                applyGain(RgbGreen(), 1.0f, &metadata, displayBoost));
+  EXPECT_RGB_EQ(applyGain(RgbBlue(), 1.0f, &metadata),
+                applyGain(RgbBlue(), 1.0f, &metadata, displayBoost));
+  EXPECT_RGB_EQ(applyGain(e, 1.0f, &metadata),
+                applyGain(e, 1.0f, &metadata, displayBoost));
 }
 
-TEST_F(RecoveryMapMathTest, GetYuv420Pixel) {
+TEST_F(GainMapMathTest, GetYuv420Pixel) {
   jpegr_uncompressed_struct image = Yuv420Image();
   Color (*colors)[4] = Yuv420Colors();
 
@@ -787,7 +834,7 @@
   }
 }
 
-TEST_F(RecoveryMapMathTest, GetP010Pixel) {
+TEST_F(GainMapMathTest, GetP010Pixel) {
   jpegr_uncompressed_struct image = P010Image();
   Color (*colors)[4] = P010Colors();
 
@@ -798,7 +845,7 @@
   }
 }
 
-TEST_F(RecoveryMapMathTest, SampleYuv420) {
+TEST_F(GainMapMathTest, SampleYuv420) {
   jpegr_uncompressed_struct image = Yuv420Image();
   Color (*colors)[4] = Yuv420Colors();
 
@@ -824,7 +871,7 @@
   }
 }
 
-TEST_F(RecoveryMapMathTest, SampleP010) {
+TEST_F(GainMapMathTest, SampleP010) {
   jpegr_uncompressed_struct image = P010Image();
   Color (*colors)[4] = P010Colors();
 
@@ -850,7 +897,7 @@
   }
 }
 
-TEST_F(RecoveryMapMathTest, SampleMap) {
+TEST_F(GainMapMathTest, SampleMap) {
   jpegr_uncompressed_struct image = MapImage();
   float (*values)[4] = MapValues();
 
@@ -890,7 +937,7 @@
   }
 }
 
-TEST_F(RecoveryMapMathTest, ColorToRgba1010102) {
+TEST_F(GainMapMathTest, ColorToRgba1010102) {
   EXPECT_EQ(colorToRgba1010102(RgbBlack()), 0x3 << 30);
   EXPECT_EQ(colorToRgba1010102(RgbWhite()), 0xFFFFFFFF);
   EXPECT_EQ(colorToRgba1010102(RgbRed()), 0x3 << 30 | 0x3ff);
@@ -905,7 +952,28 @@
           | static_cast<uint32_t>(0.3f * static_cast<float>(0x3ff)) << 20);
 }
 
-TEST_F(RecoveryMapMathTest, GenerateMapLuminanceSrgb) {
+TEST_F(GainMapMathTest, ColorToRgbaF16) {
+  EXPECT_EQ(colorToRgbaF16(RgbBlack()), ((uint64_t) 0x3C00) << 48);
+  EXPECT_EQ(colorToRgbaF16(RgbWhite()), 0x3C003C003C003C00);
+  EXPECT_EQ(colorToRgbaF16(RgbRed()),   (((uint64_t) 0x3C00) << 48) | ((uint64_t) 0x3C00));
+  EXPECT_EQ(colorToRgbaF16(RgbGreen()), (((uint64_t) 0x3C00) << 48) | (((uint64_t) 0x3C00) << 16));
+  EXPECT_EQ(colorToRgbaF16(RgbBlue()),  (((uint64_t) 0x3C00) << 48) | (((uint64_t) 0x3C00) << 32));
+
+  Color e_gamma = {{{ 0.1f, 0.2f, 0.3f }}};
+  EXPECT_EQ(colorToRgbaF16(e_gamma), 0x3C0034CD32662E66);
+}
+
+TEST_F(GainMapMathTest, Float32ToFloat16) {
+  EXPECT_EQ(floatToHalf(0.1f), 0x2E66);
+  EXPECT_EQ(floatToHalf(0.0f), 0x0);
+  EXPECT_EQ(floatToHalf(1.0f), 0x3C00);
+  EXPECT_EQ(floatToHalf(-1.0f), 0xBC00);
+  EXPECT_EQ(floatToHalf(0x1.fffffep127f), 0x7FFF);  // float max
+  EXPECT_EQ(floatToHalf(-0x1.fffffep127f), 0xFFFF);  // float min
+  EXPECT_EQ(floatToHalf(0x1.0p-126f), 0x0);  // float zero
+}
+
+TEST_F(GainMapMathTest, GenerateMapLuminanceSrgb) {
   EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), srgbLuminance),
                   0.0f);
   EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), srgbLuminance),
@@ -918,7 +986,7 @@
               srgbLuminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
 }
 
-TEST_F(RecoveryMapMathTest, GenerateMapLuminanceSrgbP3) {
+TEST_F(GainMapMathTest, GenerateMapLuminanceSrgbP3) {
   EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), p3Luminance),
                   0.0f);
   EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), p3Luminance),
@@ -931,7 +999,7 @@
               p3Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
 }
 
-TEST_F(RecoveryMapMathTest, GenerateMapLuminanceSrgbBt2100) {
+TEST_F(GainMapMathTest, GenerateMapLuminanceSrgbBt2100) {
   EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), bt2100Luminance),
                   0.0f);
   EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), bt2100Luminance),
@@ -944,7 +1012,7 @@
               bt2100Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
 }
 
-TEST_F(RecoveryMapMathTest, GenerateMapLuminanceHlg) {
+TEST_F(GainMapMathTest, GenerateMapLuminanceHlg) {
   EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), hlgInvOetf, identityConversion,
                                        bt2100Luminance, kHlgMaxNits),
                   0.0f);
@@ -962,7 +1030,7 @@
               bt2100Luminance(RgbBlue()) * kHlgMaxNits, LuminanceEpsilon());
 }
 
-TEST_F(RecoveryMapMathTest, GenerateMapLuminancePq) {
+TEST_F(GainMapMathTest, GenerateMapLuminancePq) {
   EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), pqInvOetf, identityConversion,
                                        bt2100Luminance, kPqMaxNits),
                   0.0f);
@@ -980,9 +1048,9 @@
               bt2100Luminance(RgbBlue()) * kPqMaxNits, LuminanceEpsilon());
 }
 
-TEST_F(RecoveryMapMathTest, ApplyMap) {
-  jpegr_metadata metadata = { .maxContentBoost = 8.0f,
-                              .minContentBoost = 1.0f / 8.0f };
+TEST_F(GainMapMathTest, ApplyMap) {
+  ultrahdr_metadata_struct metadata = { .maxContentBoost = 8.0f,
+                                     .minContentBoost = 1.0f / 8.0f };
 
   EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata),
                 RgbWhite() * 8.0f);
@@ -1066,4 +1134,4 @@
                 RgbWhite() / 2.0f);
 }
 
-} // namespace android::jpegrecoverymap
+} // namespace android::ultrahdr
diff --git a/libs/jpegrecoverymap/tests/jpegdecoderhelper_test.cpp b/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp
similarity index 95%
rename from libs/jpegrecoverymap/tests/jpegdecoderhelper_test.cpp
rename to libs/ultrahdr/tests/jpegdecoderhelper_test.cpp
index 2f32a56..c79dbe3 100644
--- a/libs/jpegrecoverymap/tests/jpegdecoderhelper_test.cpp
+++ b/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-#include <jpegrecoverymap/jpegdecoderhelper.h>
+#include <ultrahdr/jpegdecoderhelper.h>
 #include <gtest/gtest.h>
 #include <utils/Log.h>
 
 #include <fcntl.h>
 
-namespace android::jpegrecoverymap {
+namespace android::ultrahdr {
 
 #define YUV_IMAGE "/sdcard/Documents/minnie-320x240-yuv.jpg"
 #define YUV_IMAGE_SIZE 20193
@@ -99,4 +99,4 @@
     ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0));
 }
 
-}  // namespace android::jpegrecoverymap
\ No newline at end of file
+}  // namespace android::ultrahdr
\ No newline at end of file
diff --git a/libs/ultrahdr/tests/jpegencoderhelper_test.cpp b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp
new file mode 100644
index 0000000..8f18ac0
--- /dev/null
+++ b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ultrahdr/jpegencoderhelper.h>
+#include <gtest/gtest.h>
+#include <utils/Log.h>
+
+#include <fcntl.h>
+
+namespace android::ultrahdr {
+
+#define ALIGNED_IMAGE "/sdcard/Documents/minnie-320x240.yu12"
+#define ALIGNED_IMAGE_WIDTH 320
+#define ALIGNED_IMAGE_HEIGHT 240
+#define SINGLE_CHANNEL_IMAGE "/sdcard/Documents/minnie-320x240.y"
+#define SINGLE_CHANNEL_IMAGE_WIDTH ALIGNED_IMAGE_WIDTH
+#define SINGLE_CHANNEL_IMAGE_HEIGHT ALIGNED_IMAGE_HEIGHT
+#define UNALIGNED_IMAGE "/sdcard/Documents/minnie-318x240.yu12"
+#define UNALIGNED_IMAGE_WIDTH 318
+#define UNALIGNED_IMAGE_HEIGHT 240
+#define JPEG_QUALITY 90
+
+class JpegEncoderHelperTest : public testing::Test {
+public:
+    struct Image {
+        std::unique_ptr<uint8_t[]> buffer;
+        size_t width;
+        size_t height;
+    };
+    JpegEncoderHelperTest();
+    ~JpegEncoderHelperTest();
+protected:
+    virtual void SetUp();
+    virtual void TearDown();
+
+    Image mAlignedImage, mUnalignedImage, mSingleChannelImage;
+};
+
+JpegEncoderHelperTest::JpegEncoderHelperTest() {}
+
+JpegEncoderHelperTest::~JpegEncoderHelperTest() {}
+
+static size_t getFileSize(int fd) {
+    struct stat st;
+    if (fstat(fd, &st) < 0) {
+        ALOGW("%s : fstat failed", __func__);
+        return 0;
+    }
+    return st.st_size; // bytes
+}
+
+static bool loadFile(const char filename[], JpegEncoderHelperTest::Image* result) {
+    int fd = open(filename, O_CLOEXEC);
+    if (fd < 0) {
+        return false;
+    }
+    int length = getFileSize(fd);
+    if (length == 0) {
+        close(fd);
+        return false;
+    }
+    result->buffer.reset(new uint8_t[length]);
+    if (read(fd, result->buffer.get(), length) != static_cast<ssize_t>(length)) {
+        close(fd);
+        return false;
+    }
+    close(fd);
+    return true;
+}
+
+void JpegEncoderHelperTest::SetUp() {
+    if (!loadFile(ALIGNED_IMAGE, &mAlignedImage)) {
+        FAIL() << "Load file " << ALIGNED_IMAGE << " failed";
+    }
+    mAlignedImage.width = ALIGNED_IMAGE_WIDTH;
+    mAlignedImage.height = ALIGNED_IMAGE_HEIGHT;
+    if (!loadFile(UNALIGNED_IMAGE, &mUnalignedImage)) {
+        FAIL() << "Load file " << UNALIGNED_IMAGE << " failed";
+    }
+    mUnalignedImage.width = UNALIGNED_IMAGE_WIDTH;
+    mUnalignedImage.height = UNALIGNED_IMAGE_HEIGHT;
+    if (!loadFile(SINGLE_CHANNEL_IMAGE, &mSingleChannelImage)) {
+        FAIL() << "Load file " << SINGLE_CHANNEL_IMAGE << " failed";
+    }
+    mSingleChannelImage.width = SINGLE_CHANNEL_IMAGE_WIDTH;
+    mSingleChannelImage.height = SINGLE_CHANNEL_IMAGE_HEIGHT;
+}
+
+void JpegEncoderHelperTest::TearDown() {}
+
+TEST_F(JpegEncoderHelperTest, encodeAlignedImage) {
+    JpegEncoderHelper encoder;
+    EXPECT_TRUE(encoder.compressImage(mAlignedImage.buffer.get(), mAlignedImage.width,
+                                      mAlignedImage.height, JPEG_QUALITY, NULL, 0));
+    ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
+}
+
+// The width of the "unaligned" image is not 16-aligned, and will fail if encoded directly.
+// Should pass with the padding zero method.
+TEST_F(JpegEncoderHelperTest, encodeUnalignedImage) {
+    JpegEncoderHelper encoder;
+    const size_t paddingZeroLength = JpegEncoderHelper::kCompressBatchSize
+            * JpegEncoderHelper::kCompressBatchSize / 4;
+    std::unique_ptr<uint8_t[]> imageWithPaddingZeros(
+            new uint8_t[UNALIGNED_IMAGE_WIDTH * UNALIGNED_IMAGE_HEIGHT * 3 / 2
+            + paddingZeroLength]);
+    memcpy(imageWithPaddingZeros.get(), mUnalignedImage.buffer.get(),
+            UNALIGNED_IMAGE_WIDTH * UNALIGNED_IMAGE_HEIGHT * 3 / 2);
+    EXPECT_TRUE(encoder.compressImage(imageWithPaddingZeros.get(), mUnalignedImage.width,
+                                      mUnalignedImage.height, JPEG_QUALITY, NULL, 0));
+    ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
+}
+
+TEST_F(JpegEncoderHelperTest, encodeSingleChannelImage) {
+    JpegEncoderHelper encoder;
+    EXPECT_TRUE(encoder.compressImage(mSingleChannelImage.buffer.get(), mSingleChannelImage.width,
+                                         mSingleChannelImage.height, JPEG_QUALITY, NULL, 0, true));
+    ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
+}
+
+}  // namespace android::ultrahdr
+
diff --git a/libs/jpegrecoverymap/tests/jpegr_test.cpp b/libs/ultrahdr/tests/jpegr_test.cpp
similarity index 69%
rename from libs/jpegrecoverymap/tests/jpegr_test.cpp
rename to libs/ultrahdr/tests/jpegr_test.cpp
index 0a7d20a..58cd8f4 100644
--- a/libs/jpegrecoverymap/tests/jpegr_test.cpp
+++ b/libs/ultrahdr/tests/jpegr_test.cpp
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-#include <jpegrecoverymap/jpegr.h>
-#include <jpegrecoverymap/jpegrutils.h>
-#include <jpegrecoverymap/recoverymapmath.h>
+#include <ultrahdr/jpegr.h>
+#include <ultrahdr/jpegrutils.h>
+#include <ultrahdr/gainmapmath.h>
 #include <fcntl.h>
 #include <fstream>
 #include <gtest/gtest.h>
@@ -24,17 +24,19 @@
 #include <utils/Log.h>
 
 #define RAW_P010_IMAGE "/sdcard/Documents/raw_p010_image.p010"
+#define RAW_P010_IMAGE_WITH_STRIDE "/sdcard/Documents/raw_p010_image_with_stride.p010"
 #define RAW_YUV420_IMAGE "/sdcard/Documents/raw_yuv420_image.yuv420"
 #define JPEG_IMAGE "/sdcard/Documents/jpeg_image.jpg"
 #define TEST_IMAGE_WIDTH 1280
 #define TEST_IMAGE_HEIGHT 720
+#define TEST_IMAGE_STRIDE 1288
 #define DEFAULT_JPEG_QUALITY 90
 
 #define SAVE_ENCODING_RESULT true
 #define SAVE_DECODING_RESULT true
 #define SAVE_INPUT_RGBA true
 
-namespace android::jpegrecoverymap {
+namespace android::ultrahdr {
 
 struct Timer {
   struct timeval StartingTime;
@@ -97,6 +99,7 @@
   virtual void TearDown();
 
   struct jpegr_uncompressed_struct mRawP010Image;
+  struct jpegr_uncompressed_struct mRawP010ImageWithStride;
   struct jpegr_uncompressed_struct mRawYuv420Image;
   struct jpegr_compressed_struct mJpegImage;
 };
@@ -107,24 +110,25 @@
 void JpegRTest::SetUp() {}
 void JpegRTest::TearDown() {
   free(mRawP010Image.data);
+  free(mRawP010ImageWithStride.data);
   free(mRawYuv420Image.data);
   free(mJpegImage.data);
 }
 
 class JpegRBenchmark : public JpegR {
 public:
- void BenchmarkGenerateRecoveryMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr p010Image,
-                                   jr_metadata_ptr metadata, jr_uncompressed_ptr map);
- void BenchmarkApplyRecoveryMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map,
-                                jr_metadata_ptr metadata, jr_uncompressed_ptr dest);
+ void BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr p010Image,
+                               ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr map);
+ void BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map,
+                            ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr dest);
 private:
  const int kProfileCount = 10;
 };
 
-void JpegRBenchmark::BenchmarkGenerateRecoveryMap(jr_uncompressed_ptr yuv420Image,
-                                                        jr_uncompressed_ptr p010Image,
-                                                        jr_metadata_ptr metadata,
-                                                        jr_uncompressed_ptr map) {
+void JpegRBenchmark::BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image,
+                                              jr_uncompressed_ptr p010Image,
+                                              ultrahdr_metadata_ptr metadata,
+                                              jr_uncompressed_ptr map) {
   ASSERT_EQ(yuv420Image->width, p010Image->width);
   ASSERT_EQ(yuv420Image->height, p010Image->height);
 
@@ -132,49 +136,51 @@
 
   timerStart(&genRecMapTime);
   for (auto i = 0; i < kProfileCount; i++) {
-      ASSERT_EQ(OK, generateRecoveryMap(
-          yuv420Image, p010Image, jpegr_transfer_function::JPEGR_TF_HLG, metadata, map));
+      ASSERT_EQ(OK, generateGainMap(
+          yuv420Image, p010Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, metadata, map));
       if (i != kProfileCount - 1) delete[] static_cast<uint8_t *>(map->data);
   }
   timerStop(&genRecMapTime);
 
-  ALOGE("Generate Recovery Map:- Res = %i x %i, time = %f ms",
+  ALOGE("Generate Gain Map:- Res = %i x %i, time = %f ms",
         yuv420Image->width, yuv420Image->height,
         elapsedTime(&genRecMapTime) / (kProfileCount * 1000.f));
 
 }
 
-void JpegRBenchmark::BenchmarkApplyRecoveryMap(jr_uncompressed_ptr yuv420Image,
-                                                     jr_uncompressed_ptr map,
-                                                     jr_metadata_ptr metadata,
-                                                     jr_uncompressed_ptr dest) {
+void JpegRBenchmark::BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image,
+                                           jr_uncompressed_ptr map,
+                                           ultrahdr_metadata_ptr metadata,
+                                           jr_uncompressed_ptr dest) {
   Timer applyRecMapTime;
 
   timerStart(&applyRecMapTime);
   for (auto i = 0; i < kProfileCount; i++) {
-      ASSERT_EQ(OK, applyRecoveryMap(yuv420Image, map, metadata, JPEGR_OUTPUT_HDR_HLG, dest));
+      ASSERT_EQ(OK, applyGainMap(yuv420Image, map, metadata, ULTRAHDR_OUTPUT_HDR_HLG,
+                                 metadata->maxContentBoost /* displayBoost */, dest));
   }
   timerStop(&applyRecMapTime);
 
-  ALOGE("Apply Recovery Map:- Res = %i x %i, time = %f ms",
+  ALOGE("Apply Gain Map:- Res = %i x %i, time = %f ms",
         yuv420Image->width, yuv420Image->height,
         elapsedTime(&applyRecMapTime) / (kProfileCount * 1000.f));
 }
 
 TEST_F(JpegRTest, build) {
-  // Force all of the recovery map lib to be linked by calling all public functions.
+  // Force all of the gain map lib to be linked by calling all public functions.
   JpegR jpegRCodec;
-  jpegRCodec.encodeJPEGR(nullptr, static_cast<jpegr_transfer_function>(0), nullptr, 0, nullptr);
-  jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast<jpegr_transfer_function>(0),
+  jpegRCodec.encodeJPEGR(nullptr, static_cast<ultrahdr_transfer_function>(0), nullptr, 0, nullptr);
+  jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast<ultrahdr_transfer_function>(0),
                          nullptr, 0, nullptr);
-  jpegRCodec.encodeJPEGR(nullptr, nullptr, nullptr, static_cast<jpegr_transfer_function>(0),
+  jpegRCodec.encodeJPEGR(nullptr, nullptr, nullptr, static_cast<ultrahdr_transfer_function>(0),
                          nullptr);
-  jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast<jpegr_transfer_function>(0), nullptr);
-  jpegRCodec.decodeJPEGR(nullptr, nullptr, nullptr);
+  jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast<ultrahdr_transfer_function>(0), nullptr);
+  jpegRCodec.decodeJPEGR(nullptr, nullptr);
 }
 
 TEST_F(JpegRTest, writeXmpThenRead) {
-  jpegr_metadata metadata_expected;
+  ultrahdr_metadata_struct metadata_expected;
+  metadata_expected.version = "1.0";
   metadata_expected.maxContentBoost = 1.25;
   metadata_expected.minContentBoost = 0.75;
   const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
@@ -189,10 +195,10 @@
   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;
+  ultrahdr_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);
+  EXPECT_FLOAT_EQ(metadata_expected.maxContentBoost, metadata_read.maxContentBoost);
+  EXPECT_FLOAT_EQ(metadata_expected.minContentBoost, metadata_read.minContentBoost);
 }
 
 /* Test Encode API-0 and decode */
@@ -205,7 +211,7 @@
   }
   mRawP010Image.width = TEST_IMAGE_WIDTH;
   mRawP010Image.height = TEST_IMAGE_HEIGHT;
-  mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
+  mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
 
   JpegR jpegRCodec;
 
@@ -213,7 +219,63 @@
   jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
   jpegR.data = malloc(jpegR.maxLength);
   ret = jpegRCodec.encodeJPEGR(
-      &mRawP010Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR, DEFAULT_JPEG_QUALITY, nullptr);
+      &mRawP010Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, DEFAULT_JPEG_QUALITY,
+      nullptr);
+  if (ret != OK) {
+    FAIL() << "Error code is " << ret;
+  }
+  if (SAVE_ENCODING_RESULT) {
+    // Output image data to file
+    std::string filePath = "/sdcard/Documents/encoded_from_p010_input.jpgr";
+    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
+    if (!imageFile.is_open()) {
+      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
+    }
+    imageFile.write((const char*)jpegR.data, jpegR.length);
+  }
+
+  jpegr_uncompressed_struct decodedJpegR;
+  int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8;
+  decodedJpegR.data = malloc(decodedJpegRSize);
+  ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR);
+  if (ret != OK) {
+    FAIL() << "Error code is " << ret;
+  }
+  if (SAVE_DECODING_RESULT) {
+    // Output image data to file
+    std::string filePath = "/sdcard/Documents/decoded_from_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());
+    }
+    imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize);
+  }
+
+  free(jpegR.data);
+  free(decodedJpegR.data);
+}
+
+/* Test Encode API-0 (with stride) and decode */
+TEST_F(JpegRTest, encodeFromP010WithStrideThenDecode) {
+  int ret;
+
+  // Load input files.
+  if (!loadFile(RAW_P010_IMAGE_WITH_STRIDE, mRawP010ImageWithStride.data, nullptr)) {
+    FAIL() << "Load file " << RAW_P010_IMAGE_WITH_STRIDE << " failed";
+  }
+  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+  mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
+  mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+
+  JpegR jpegRCodec;
+
+  jpegr_compressed_struct jpegR;
+  jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
+  jpegR.data = malloc(jpegR.maxLength);
+  ret = jpegRCodec.encodeJPEGR(
+      &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
+      DEFAULT_JPEG_QUALITY, nullptr);
   if (ret != OK) {
     FAIL() << "Error code is " << ret;
   }
@@ -258,14 +320,14 @@
   }
   mRawP010Image.width = TEST_IMAGE_WIDTH;
   mRawP010Image.height = TEST_IMAGE_HEIGHT;
-  mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
+  mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
 
   if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) {
     FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
   }
   mRawYuv420Image.width = TEST_IMAGE_WIDTH;
   mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
-  mRawYuv420Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
+  mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
 
   JpegR jpegRCodec;
 
@@ -273,7 +335,7 @@
   jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
   jpegR.data = malloc(jpegR.maxLength);
   ret = jpegRCodec.encodeJPEGR(
-      &mRawP010Image, &mRawYuv420Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR,
+      &mRawP010Image, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
       DEFAULT_JPEG_QUALITY, nullptr);
   if (ret != OK) {
     FAIL() << "Error code is " << ret;
@@ -319,19 +381,19 @@
   }
   mRawP010Image.width = TEST_IMAGE_WIDTH;
   mRawP010Image.height = TEST_IMAGE_HEIGHT;
-  mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
+  mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
 
   if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) {
     FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
   }
   mRawYuv420Image.width = TEST_IMAGE_WIDTH;
   mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
-  mRawYuv420Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
+  mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
 
   if (!loadFile(JPEG_IMAGE, mJpegImage.data, &mJpegImage.length)) {
     FAIL() << "Load file " << JPEG_IMAGE << " failed";
   }
-  mJpegImage.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
+  mJpegImage.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
 
   JpegR jpegRCodec;
 
@@ -339,7 +401,8 @@
   jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
   jpegR.data = malloc(jpegR.maxLength);
   ret = jpegRCodec.encodeJPEGR(
-      &mRawP010Image, &mRawYuv420Image, &mJpegImage, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR);
+      &mRawP010Image, &mRawYuv420Image, &mJpegImage, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+      &jpegR);
   if (ret != OK) {
     FAIL() << "Error code is " << ret;
   }
@@ -384,7 +447,7 @@
   }
   mRawP010Image.width = TEST_IMAGE_WIDTH;
   mRawP010Image.height = TEST_IMAGE_HEIGHT;
-  mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
+  mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
 
   if (SAVE_INPUT_RGBA) {
     size_t rgbaSize = mRawP010Image.width * mRawP010Image.height * sizeof(uint32_t);
@@ -412,7 +475,7 @@
   if (!loadFile(JPEG_IMAGE, mJpegImage.data, &mJpegImage.length)) {
     FAIL() << "Load file " << JPEG_IMAGE << " failed";
   }
-  mJpegImage.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
+  mJpegImage.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
 
   JpegR jpegRCodec;
 
@@ -420,7 +483,7 @@
   jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
   jpegR.data = malloc(jpegR.maxLength);
   ret = jpegRCodec.encodeJPEGR(
-      &mRawP010Image, &mJpegImage, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR);
+      &mRawP010Image, &mJpegImage, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR);
   if (ret != OK) {
     FAIL() << "Error code is " << ret;
   }
@@ -455,7 +518,7 @@
   free(decodedJpegR.data);
 }
 
-TEST_F(JpegRTest, ProfileRecoveryMapFuncs) {
+TEST_F(JpegRTest, ProfileGainMapFuncs) {
   const size_t kWidth = TEST_IMAGE_WIDTH;
   const size_t kHeight = TEST_IMAGE_HEIGHT;
 
@@ -465,36 +528,36 @@
   }
   mRawP010Image.width = kWidth;
   mRawP010Image.height = kHeight;
-  mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
+  mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
 
   if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) {
     FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
   }
   mRawYuv420Image.width = kWidth;
   mRawYuv420Image.height = kHeight;
-  mRawYuv420Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
+  mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
 
   JpegRBenchmark benchmark;
 
-  jpegr_metadata metadata = { .version = 1,
+  ultrahdr_metadata_struct metadata = { .version = "1.0",
                               .maxContentBoost = 8.0f,
                               .minContentBoost = 1.0f / 8.0f };
 
   jpegr_uncompressed_struct map = { .data = NULL,
                                     .width = 0,
                                     .height = 0,
-                                    .colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED };
+                                    .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED };
 
-  benchmark.BenchmarkGenerateRecoveryMap(&mRawYuv420Image, &mRawP010Image, &metadata, &map);
+  benchmark.BenchmarkGenerateGainMap(&mRawYuv420Image, &mRawP010Image, &metadata, &map);
 
   const int dstSize = mRawYuv420Image.width * mRawYuv420Image.height * 4;
   auto bufferDst = std::make_unique<uint8_t[]>(dstSize);
   jpegr_uncompressed_struct dest = { .data = bufferDst.get(),
                                      .width = 0,
                                      .height = 0,
-                                     .colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED };
+                                     .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED };
 
-  benchmark.BenchmarkApplyRecoveryMap(&mRawYuv420Image, &map, &metadata, &dest);
+  benchmark.BenchmarkApplyGainMap(&mRawYuv420Image, &map, &metadata, &dest);
 }
 
-} // namespace android::recoverymap
+} // namespace android::ultrahdr
diff --git a/opengl/include/EGL/eglext.h b/opengl/include/EGL/eglext.h
index 501bf58..32c21f6 100644
--- a/opengl/include/EGL/eglext.h
+++ b/opengl/include/EGL/eglext.h
@@ -697,6 +697,11 @@
 #define EGL_EXT_device_query 1
 #endif /* EGL_EXT_device_query */
 
+#ifndef EGL_EXT_gl_colorspace_bt2020_hlg
+#define EGL_EXT_gl_colorspace_bt2020_hlg 1
+#define EGL_GL_COLORSPACE_BT2020_HLG_EXT  0x333E
+#endif /* EGL_EXT_gl_colorspace_bt2020_hlg */
+
 #ifndef EGL_EXT_gl_colorspace_bt2020_linear
 #define EGL_EXT_gl_colorspace_bt2020_linear 1
 #define EGL_GL_COLORSPACE_BT2020_LINEAR_EXT 0x333F
diff --git a/opengl/libs/Android.bp b/opengl/libs/Android.bp
index 49e1cba..16de390 100644
--- a/opengl/libs/Android.bp
+++ b/opengl/libs/Android.bp
@@ -205,6 +205,7 @@
     srcs: [
         "EGL/BlobCache.cpp",
         "EGL/BlobCache_test.cpp",
+        "EGL/FileBlobCache.cpp",
         "EGL/MultifileBlobCache.cpp",
         "EGL/MultifileBlobCache_test.cpp",
     ],
diff --git a/opengl/libs/EGL/BlobCache.cpp b/opengl/libs/EGL/BlobCache.cpp
index aecfc6b..b3a4bc1 100644
--- a/opengl/libs/EGL/BlobCache.cpp
+++ b/opengl/libs/EGL/BlobCache.cpp
@@ -15,6 +15,7 @@
  */
 
 //#define LOG_NDEBUG 0
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include "BlobCache.h"
 
@@ -22,6 +23,7 @@
 #include <errno.h>
 #include <inttypes.h>
 #include <log/log.h>
+#include <utils/Trace.h>
 
 #include <chrono>
 
@@ -230,6 +232,8 @@
 }
 
 int BlobCache::unflatten(void const* buffer, size_t size) {
+    ATRACE_NAME("BlobCache::unflatten");
+
     // All errors should result in the BlobCache being in an empty state.
     clear();
 
@@ -293,6 +297,8 @@
 }
 
 void BlobCache::clean() {
+    ATRACE_NAME("BlobCache::clean");
+
     // Remove a random cache entry until the total cache size gets below half
     // the maximum total cache size.
     while (mTotalSize > mMaxTotalSize / 2) {
diff --git a/opengl/libs/EGL/FileBlobCache.cpp b/opengl/libs/EGL/FileBlobCache.cpp
index 3f7ae7e..4a0fac4 100644
--- a/opengl/libs/EGL/FileBlobCache.cpp
+++ b/opengl/libs/EGL/FileBlobCache.cpp
@@ -14,6 +14,8 @@
  ** limitations under the License.
  */
 
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
 #include "FileBlobCache.h"
 
 #include <errno.h>
@@ -24,6 +26,7 @@
 #include <unistd.h>
 
 #include <log/log.h>
+#include <utils/Trace.h>
 
 // Cache file header
 static const char* cacheFileMagic = "EGL$";
@@ -31,7 +34,7 @@
 
 namespace android {
 
-static uint32_t crc32c(const uint8_t* buf, size_t len) {
+uint32_t crc32c(const uint8_t* buf, size_t len) {
     const uint32_t polyBits = 0x82F63B78;
     uint32_t r = 0;
     for (size_t i = 0; i < len; i++) {
@@ -51,6 +54,8 @@
         const std::string& filename)
         : BlobCache(maxKeySize, maxValueSize, maxTotalSize)
         , mFilename(filename) {
+    ATRACE_CALL();
+
     if (mFilename.length() > 0) {
         size_t headerSize = cacheFileHeaderSize;
 
@@ -117,6 +122,8 @@
 }
 
 void FileBlobCache::writeToFile() {
+    ATRACE_CALL();
+
     if (mFilename.length() > 0) {
         size_t cacheSize = getFlattenedSize();
         size_t headerSize = cacheFileHeaderSize;
diff --git a/opengl/libs/EGL/FileBlobCache.h b/opengl/libs/EGL/FileBlobCache.h
index 8220723..f083b0d 100644
--- a/opengl/libs/EGL/FileBlobCache.h
+++ b/opengl/libs/EGL/FileBlobCache.h
@@ -22,6 +22,8 @@
 
 namespace android {
 
+uint32_t crc32c(const uint8_t* buf, size_t len);
+
 class FileBlobCache : public BlobCache {
 public:
     // FileBlobCache attempts to load the saved cache contents from disk into
diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp
index 34b1251..2c3ce16 100644
--- a/opengl/libs/EGL/Loader.cpp
+++ b/opengl/libs/EGL/Loader.cpp
@@ -21,6 +21,7 @@
 
 #include <android-base/properties.h>
 #include <android/dlext.h>
+#include <cutils/properties.h>
 #include <dirent.h>
 #include <dlfcn.h>
 #include <graphicsenv/GraphicsEnv.h>
@@ -138,9 +139,10 @@
 
 static const char* DRIVER_SUFFIX_PROPERTY = "ro.hardware.egl";
 
-static const char* HAL_SUBNAME_KEY_PROPERTIES[2] = {
-    DRIVER_SUFFIX_PROPERTY,
-    "ro.board.platform",
+static const char* HAL_SUBNAME_KEY_PROPERTIES[3] = {
+        "persist.graphics.egl",
+        DRIVER_SUFFIX_PROPERTY,
+        "ro.board.platform",
 };
 
 static bool should_unload_system_driver(egl_connection_t* cnx) {
@@ -207,8 +209,7 @@
     ATRACE_CALL();
     const nsecs_t openTime = systemTime();
 
-    if (!android::GraphicsEnv::getInstance().angleIsSystemDriver() &&
-        should_unload_system_driver(cnx)) {
+    if (should_unload_system_driver(cnx)) {
         unload_system_driver(cnx);
     }
 
@@ -217,12 +218,8 @@
         return cnx->dso;
     }
 
-    // Firstly, try to load ANGLE driver, unless we know that we shouldn't.
-    bool shouldForceLegacyDriver = android::GraphicsEnv::getInstance().shouldForceLegacyDriver();
-    driver_t* hnd = nullptr;
-    if (!shouldForceLegacyDriver) {
-        hnd = attempt_to_load_angle(cnx);
-    }
+    // Firstly, try to load ANGLE driver.
+    driver_t* hnd = attempt_to_load_angle(cnx);
 
     if (!hnd) {
         // Secondly, try to load from driver apk.
@@ -236,29 +233,22 @@
             LOG_ALWAYS_FATAL("couldn't find an OpenGL ES implementation from %s",
                              android::GraphicsEnv::getInstance().getDriverPath().c_str());
         }
-        // Finally, try to load system driver.  If ANGLE is the system driver
-        // (i.e. we are forcing the legacy system driver instead of ANGLE), use
-        // the driver suffix that was passed down from above.
-        if (shouldForceLegacyDriver) {
-            std::string suffix = android::GraphicsEnv::getInstance().getLegacySuffix();
-            hnd = attempt_to_load_system_driver(cnx, suffix.c_str(), true);
-        } else {
-            // Start by searching for the library name appended by the system
-            // properties of the GLES userspace driver in both locations.
-            // i.e.:
-            //      libGLES_${prop}.so, or:
-            //      libEGL_${prop}.so, libGLESv1_CM_${prop}.so, libGLESv2_${prop}.so
-            for (auto key : HAL_SUBNAME_KEY_PROPERTIES) {
-                auto prop = base::GetProperty(key, "");
-                if (prop.empty()) {
-                    continue;
-                }
-                hnd = attempt_to_load_system_driver(cnx, prop.c_str(), true);
-                if (hnd) {
-                    break;
-                } else if (strcmp(key, DRIVER_SUFFIX_PROPERTY) == 0) {
-                    failToLoadFromDriverSuffixProperty = true;
-                }
+        // Finally, try to load system driver.
+        // Start by searching for the library name appended by the system
+        // properties of the GLES userspace driver in both locations.
+        // i.e.:
+        //      libGLES_${prop}.so, or:
+        //      libEGL_${prop}.so, libGLESv1_CM_${prop}.so, libGLESv2_${prop}.so
+        for (auto key : HAL_SUBNAME_KEY_PROPERTIES) {
+            auto prop = base::GetProperty(key, "");
+            if (prop.empty()) {
+                continue;
+            }
+            hnd = attempt_to_load_system_driver(cnx, prop.c_str(), true);
+            if (hnd) {
+                break;
+            } else if (strcmp(key, DRIVER_SUFFIX_PROPERTY) == 0) {
+                failToLoadFromDriverSuffixProperty = true;
             }
         }
     }
@@ -272,7 +262,10 @@
         hnd = attempt_to_load_system_driver(cnx, nullptr, true);
     }
 
-    if (!hnd && !failToLoadFromDriverSuffixProperty) {
+    if (!hnd && !failToLoadFromDriverSuffixProperty &&
+        property_get_int32("ro.vendor.api_level", 0) < __ANDROID_API_U__) {
+        // Still can't find the graphics drivers with the exact name. This time try to use wildcard
+        // matching if the device is launched before Android 14.
         hnd = attempt_to_load_system_driver(cnx, nullptr, false);
     }
 
@@ -288,8 +281,10 @@
     }
 
     LOG_ALWAYS_FATAL_IF(!hnd,
-                        "couldn't find an OpenGL ES implementation, make sure you set %s or %s",
-                        HAL_SUBNAME_KEY_PROPERTIES[0], HAL_SUBNAME_KEY_PROPERTIES[1]);
+                        "couldn't find an OpenGL ES implementation, make sure one of %s, %s and %s "
+                        "is set",
+                        HAL_SUBNAME_KEY_PROPERTIES[0], HAL_SUBNAME_KEY_PROPERTIES[1],
+                        HAL_SUBNAME_KEY_PROPERTIES[2]);
 
     if (!cnx->libEgl) {
         cnx->libEgl = load_wrapper(EGL_WRAPPER_DIR "/libEGL.so");
diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp
index 99af299..7ffdac7 100644
--- a/opengl/libs/EGL/MultifileBlobCache.cpp
+++ b/opengl/libs/EGL/MultifileBlobCache.cpp
@@ -39,22 +39,11 @@
 
 using namespace std::literals;
 
+constexpr uint32_t kMultifileMagic = 'MFB$';
+constexpr uint32_t kCrcPlaceholder = 0;
+
 namespace {
 
-// Open the file and determine the size of the value it contains
-size_t getValueSizeFromFile(int fd, const std::string& entryPath) {
-    // Read the beginning of the file to get header
-    android::MultifileHeader header;
-    size_t result = read(fd, static_cast<void*>(&header), sizeof(android::MultifileHeader));
-    if (result != sizeof(android::MultifileHeader)) {
-        ALOGE("Error reading MultifileHeader from cache entry (%s): %s", entryPath.c_str(),
-              std::strerror(errno));
-        return 0;
-    }
-
-    return header.valueSize;
-}
-
 // Helper function to close entries or free them
 void freeHotCacheEntry(android::MultifileHotCache& entry) {
     if (entry.entryFd != -1) {
@@ -73,12 +62,14 @@
 
 namespace android {
 
-MultifileBlobCache::MultifileBlobCache(size_t maxTotalSize, size_t maxHotCacheSize,
+MultifileBlobCache::MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize,
                                        const std::string& baseDir)
       : mInitialized(false),
+        mMaxKeySize(maxKeySize),
+        mMaxValueSize(maxValueSize),
         mMaxTotalSize(maxTotalSize),
         mTotalCacheSize(0),
-        mHotCacheLimit(maxHotCacheSize),
+        mHotCacheLimit(0),
         mHotCacheSize(0),
         mWorkerThreadIdle(true) {
     if (baseDir.empty()) {
@@ -89,9 +80,9 @@
     // Establish the name of our multifile directory
     mMultifileDirName = baseDir + ".multifile";
 
-    // Set a limit for max key and value, ensuring at least one entry can always fit in hot cache
-    mMaxKeySize = mHotCacheLimit / 4;
-    mMaxValueSize = mHotCacheLimit / 2;
+    // Set the hotcache limit to be large enough to contain one max entry
+    // This ensure the hot cache is always large enough for single entry
+    mHotCacheLimit = mMaxKeySize + mMaxValueSize + sizeof(MultifileHeader);
 
     ALOGV("INIT: Initializing multifile blobcache with maxKeySize=%zu and maxValueSize=%zu",
           mMaxKeySize, mMaxValueSize);
@@ -129,6 +120,15 @@
                     return;
                 }
 
+                // If the cache entry is damaged or no good, remove it
+                if (st.st_size <= 0 || st.st_atime <= 0) {
+                    ALOGE("INIT: Entry %u has invalid stats! Removing.", entryHash);
+                    if (remove(fullPath.c_str()) != 0) {
+                        ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+                    }
+                    continue;
+                }
+
                 // Open the file so we can read its header
                 int fd = open(fullPath.c_str(), O_RDONLY);
                 if (fd == -1) {
@@ -137,13 +137,51 @@
                     return;
                 }
 
-                // Look up the details we track about each file
-                size_t valueSize = getValueSizeFromFile(fd, fullPath);
+                // Read the beginning of the file to get header
+                MultifileHeader header;
+                size_t result = read(fd, static_cast<void*>(&header), sizeof(MultifileHeader));
+                if (result != sizeof(MultifileHeader)) {
+                    ALOGE("Error reading MultifileHeader from cache entry (%s): %s",
+                          fullPath.c_str(), std::strerror(errno));
+                    return;
+                }
+
+                // Verify header magic
+                if (header.magic != kMultifileMagic) {
+                    ALOGE("INIT: Entry %u has bad magic (%u)! Removing.", entryHash, header.magic);
+                    if (remove(fullPath.c_str()) != 0) {
+                        ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+                    }
+                    continue;
+                }
+
+                // Note: Converting from off_t (signed) to size_t (unsigned)
+                size_t fileSize = static_cast<size_t>(st.st_size);
+
+                // Memory map the file
+                uint8_t* mappedEntry = reinterpret_cast<uint8_t*>(
+                        mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
+                if (mappedEntry == MAP_FAILED) {
+                    ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
+                    return;
+                }
+
+                // Ensure we have a good CRC
+                if (header.crc !=
+                    crc32c(mappedEntry + sizeof(MultifileHeader),
+                           fileSize - sizeof(MultifileHeader))) {
+                    ALOGE("INIT: Entry %u failed CRC check! Removing.", entryHash);
+                    if (remove(fullPath.c_str()) != 0) {
+                        ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+                    }
+                    continue;
+                }
 
                 // If the cache entry is damaged or no good, remove it
-                // TODO: Perform any other checks
-                if (valueSize <= 0 || st.st_size <= 0 || st.st_atime <= 0) {
-                    ALOGV("INIT: Entry %u has a problem! Removing.", entryHash);
+                if (header.keySize <= 0 || header.valueSize <= 0) {
+                    ALOGE("INIT: Entry %u has a bad header keySize (%lu) or valueSize (%lu), "
+                          "removing.",
+                          entryHash, header.keySize, header.valueSize);
                     if (remove(fullPath.c_str()) != 0) {
                         ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
                     }
@@ -152,25 +190,14 @@
 
                 ALOGV("INIT: Entry %u is good, tracking it now.", entryHash);
 
-                // Note: Converting from off_t (signed) to size_t (unsigned)
-                size_t fileSize = static_cast<size_t>(st.st_size);
-                time_t accessTime = st.st_atime;
-
                 // Track details for rapid lookup later
-                trackEntry(entryHash, valueSize, fileSize, accessTime);
+                trackEntry(entryHash, header.valueSize, fileSize, st.st_atime);
 
                 // Track the total size
                 increaseTotalCacheSize(fileSize);
 
                 // Preload the entry for fast retrieval
                 if ((mHotCacheSize + fileSize) < mHotCacheLimit) {
-                    // Memory map the file
-                    uint8_t* mappedEntry = reinterpret_cast<uint8_t*>(
-                            mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
-                    if (mappedEntry == MAP_FAILED) {
-                        ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
-                    }
-
                     ALOGV("INIT: Populating hot cache with fd = %i, cacheEntry = %p for "
                           "entryHash %u",
                           fd, mappedEntry, entryHash);
@@ -183,6 +210,8 @@
                         return;
                     }
                 } else {
+                    // If we're not keeping it in hot cache, unmap it now
+                    munmap(mappedEntry, fileSize);
                     close(fd);
                 }
             }
@@ -227,7 +256,7 @@
 
     // Ensure key and value are under their limits
     if (keySize > mMaxKeySize || valueSize > mMaxValueSize) {
-        ALOGV("SET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
+        ALOGW("SET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
               valueSize, mMaxValueSize);
         return;
     }
@@ -240,17 +269,18 @@
     // If we're going to be over the cache limit, kick off a trim to clear space
     if (getTotalSize() + fileSize > mMaxTotalSize) {
         ALOGV("SET: Cache is full, calling trimCache to clear space");
-        trimCache(mMaxTotalSize);
+        trimCache();
     }
 
     ALOGV("SET: Add %u to cache", entryHash);
 
     uint8_t* buffer = new uint8_t[fileSize];
 
-    // Write the key and value after the header
-    android::MultifileHeader header = {keySize, valueSize};
+    // Write placeholders for magic and CRC until deferred thread completes the write
+    android::MultifileHeader header = {kMultifileMagic, kCrcPlaceholder, keySize, valueSize};
     memcpy(static_cast<void*>(buffer), static_cast<const void*>(&header),
            sizeof(android::MultifileHeader));
+    // Write the key and value after the header
     memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader)), static_cast<const void*>(key),
            keySize);
     memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader) + keySize),
@@ -269,13 +299,18 @@
 
     // Sending -1 as the fd indicates we don't have an fd for this
     if (!addToHotCache(entryHash, -1, buffer, fileSize)) {
-        ALOGE("GET: Failed to add %u to hot cache", entryHash);
+        ALOGE("SET: Failed to add %u to hot cache", entryHash);
+        delete[] buffer;
         return;
     }
 
     // Track that we're creating a pending write for this entry
     // Include the buffer to handle the case when multiple writes are pending for an entry
-    mDeferredWrites.insert(std::make_pair(entryHash, buffer));
+    {
+        // Synchronize access to deferred write status
+        std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
+        mDeferredWrites.insert(std::make_pair(entryHash, buffer));
+    }
 
     // Create deferred task to write to storage
     ALOGV("SET: Adding task to queue.");
@@ -293,7 +328,7 @@
 
     // Ensure key and value are under their limits
     if (keySize > mMaxKeySize || valueSize > mMaxValueSize) {
-        ALOGV("GET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
+        ALOGW("GET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
               valueSize, mMaxValueSize);
         return 0;
     }
@@ -342,8 +377,15 @@
     } else {
         ALOGV("GET: HotCache MISS for entry: %u", entryHash);
 
-        if (mDeferredWrites.find(entryHash) != mDeferredWrites.end()) {
-            // Wait for writes to complete if there is an outstanding write for this entry
+        // Wait for writes to complete if there is an outstanding write for this entry
+        bool wait = false;
+        {
+            // Synchronize access to deferred write status
+            std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
+            wait = mDeferredWrites.find(entryHash) != mDeferredWrites.end();
+        }
+
+        if (wait) {
             ALOGV("GET: Waiting for write to complete for %u", entryHash);
             waitForWorkComplete();
         }
@@ -455,6 +497,7 @@
               mHotCacheSize, newEntrySize, mHotCacheLimit, newEntryHash);
 
         // Wait for all the files to complete writing so our hot cache is accurate
+        ALOGV("HOTCACHE(ADD): Waiting for work to complete for %u", newEntryHash);
         waitForWorkComplete();
 
         // Free up old entries until under the limit
@@ -491,6 +534,7 @@
         ALOGV("HOTCACHE(REMOVE): Removing %u from hot cache", entryHash);
 
         // Wait for all the files to complete writing so our hot cache is accurate
+        ALOGV("HOTCACHE(REMOVE): Waiting for work to complete for %u", entryHash);
         waitForWorkComplete();
 
         ALOGV("HOTCACHE(REMOVE): Closing hot cache entry for %u", entryHash);
@@ -547,7 +591,7 @@
         }
     }
 
-    ALOGV("LRU: Cache is emptry");
+    ALOGV("LRU: Cache is empty");
     return false;
 }
 
@@ -556,23 +600,15 @@
 constexpr uint32_t kCacheLimitDivisor = 2;
 
 // Calculate the cache size and remove old entries until under the limit
-void MultifileBlobCache::trimCache(size_t cacheByteLimit) {
-    // Start with the value provided by egl_cache
-    size_t limit = cacheByteLimit;
-
+void MultifileBlobCache::trimCache() {
     // Wait for all deferred writes to complete
+    ALOGV("TRIM: Waiting for work to complete.");
     waitForWorkComplete();
 
-    size_t size = getTotalSize();
-
-    // If size is larger than the threshold, remove files using LRU
-    if (size > limit) {
-        ALOGV("TRIM: Multifile cache size is larger than %zu, removing old entries",
-              cacheByteLimit);
-        if (!applyLRU(limit / kCacheLimitDivisor)) {
-            ALOGE("Error when clearing multifile shader cache");
-            return;
-        }
+    ALOGV("TRIM: Reducing multifile cache size to %zu", mMaxTotalSize / kCacheLimitDivisor);
+    if (!applyLRU(mMaxTotalSize / kCacheLimitDivisor)) {
+        ALOGE("Error when clearing multifile shader cache");
+        return;
     }
 }
 
@@ -600,6 +636,11 @@
 
             ALOGV("DEFERRED: Opened fd %i from %s", fd, fullPath.c_str());
 
+            // Add CRC check to the header (always do this last!)
+            MultifileHeader* header = reinterpret_cast<MultifileHeader*>(buffer);
+            header->crc =
+                    crc32c(buffer + sizeof(MultifileHeader), bufferSize - sizeof(MultifileHeader));
+
             ssize_t result = write(fd, buffer, bufferSize);
             if (result != bufferSize) {
                 ALOGE("Error writing fileSize to cache entry (%s): %s", fullPath.c_str(),
@@ -612,13 +653,18 @@
 
             // Erase the entry from mDeferredWrites
             // Since there could be multiple outstanding writes for an entry, find the matching one
-            typedef std::multimap<uint32_t, uint8_t*>::iterator entryIter;
-            std::pair<entryIter, entryIter> iterPair = mDeferredWrites.equal_range(entryHash);
-            for (entryIter it = iterPair.first; it != iterPair.second; ++it) {
-                if (it->second == buffer) {
-                    ALOGV("DEFERRED: Marking write complete for %u at %p", it->first, it->second);
-                    mDeferredWrites.erase(it);
-                    break;
+            {
+                // Synchronize access to deferred write status
+                std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
+                typedef std::multimap<uint32_t, uint8_t*>::iterator entryIter;
+                std::pair<entryIter, entryIter> iterPair = mDeferredWrites.equal_range(entryHash);
+                for (entryIter it = iterPair.first; it != iterPair.second; ++it) {
+                    if (it->second == buffer) {
+                        ALOGV("DEFERRED: Marking write complete for %u at %p", it->first,
+                              it->second);
+                        mDeferredWrites.erase(it);
+                        break;
+                    }
                 }
             }
 
@@ -686,4 +732,4 @@
     mWorkerIdleCondition.wait(lock, [this] { return (mTasks.empty() && mWorkerThreadIdle); });
 }
 
-}; // namespace android
\ No newline at end of file
+}; // namespace android
diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h
index c0cc9dc..5e527dc 100644
--- a/opengl/libs/EGL/MultifileBlobCache.h
+++ b/opengl/libs/EGL/MultifileBlobCache.h
@@ -20,6 +20,7 @@
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
 
+#include <android-base/thread_annotations.h>
 #include <future>
 #include <map>
 #include <queue>
@@ -28,9 +29,13 @@
 #include <unordered_map>
 #include <unordered_set>
 
+#include "FileBlobCache.h"
+
 namespace android {
 
 struct MultifileHeader {
+    uint32_t magic;
+    uint32_t crc;
     EGLsizeiANDROID keySize;
     EGLsizeiANDROID valueSize;
 };
@@ -86,7 +91,8 @@
 
 class MultifileBlobCache {
 public:
-    MultifileBlobCache(size_t maxTotalSize, size_t maxHotCacheSize, const std::string& baseDir);
+    MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize,
+                       const std::string& baseDir);
     ~MultifileBlobCache();
 
     void set(const void* key, EGLsizeiANDROID keySize, const void* value,
@@ -114,7 +120,7 @@
     bool addToHotCache(uint32_t entryHash, int fd, uint8_t* entryBufer, size_t entrySize);
     bool removeFromHotCache(uint32_t entryHash);
 
-    void trimCache(size_t cacheByteLimit);
+    void trimCache();
     bool applyLRU(size_t cacheLimit);
 
     bool mInitialized;
@@ -135,7 +141,8 @@
     // Below are the components used for deferred writes
 
     // Track whether we have pending writes for an entry
-    std::multimap<uint32_t, uint8_t*> mDeferredWrites;
+    std::mutex mDeferredWriteStatusMutex;
+    std::multimap<uint32_t, uint8_t*> mDeferredWrites GUARDED_BY(mDeferredWriteStatusMutex);
 
     // Functions to work through tasks in the queue
     void processTasks();
diff --git a/opengl/libs/EGL/MultifileBlobCache_test.cpp b/opengl/libs/EGL/MultifileBlobCache_test.cpp
index 1a55a4f..dbee13b 100644
--- a/opengl/libs/EGL/MultifileBlobCache_test.cpp
+++ b/opengl/libs/EGL/MultifileBlobCache_test.cpp
@@ -28,17 +28,16 @@
 template <typename T>
 using sp = std::shared_ptr<T>;
 
+constexpr size_t kMaxKeySize = 2 * 1024;
+constexpr size_t kMaxValueSize = 6 * 1024;
 constexpr size_t kMaxTotalSize = 32 * 1024;
-constexpr size_t kMaxPreloadSize = 8 * 1024;
-
-constexpr size_t kMaxKeySize = kMaxPreloadSize / 4;
-constexpr size_t kMaxValueSize = kMaxPreloadSize / 2;
 
 class MultifileBlobCacheTest : public ::testing::Test {
 protected:
     virtual void SetUp() {
         mTempFile.reset(new TemporaryFile());
-        mMBC.reset(new MultifileBlobCache(kMaxTotalSize, kMaxPreloadSize, &mTempFile->path[0]));
+        mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize,
+                                          &mTempFile->path[0]));
     }
 
     virtual void TearDown() { mMBC.reset(); }
@@ -190,6 +189,26 @@
     }
 }
 
+TEST_F(MultifileBlobCacheTest, CacheMaxKeyAndValueSizeSucceeds) {
+    char key[kMaxKeySize];
+    for (int i = 0; i < kMaxKeySize; i++) {
+        key[i] = 'a';
+    }
+    char buf[kMaxValueSize];
+    for (int i = 0; i < kMaxValueSize; i++) {
+        buf[i] = 'b';
+    }
+    mMBC->set(key, kMaxKeySize, buf, kMaxValueSize);
+    for (int i = 0; i < kMaxValueSize; i++) {
+        buf[i] = 0xee;
+    }
+    mMBC->get(key, kMaxKeySize, buf, kMaxValueSize);
+    for (int i = 0; i < kMaxValueSize; i++) {
+        SCOPED_TRACE(i);
+        ASSERT_EQ('b', buf[i]);
+    }
+}
+
 TEST_F(MultifileBlobCacheTest, CacheMinKeyAndValueSizeSucceeds) {
     unsigned char buf[1] = {0xee};
     mMBC->set("x", 1, "y", 1);
diff --git a/opengl/libs/EGL/egl_cache.cpp b/opengl/libs/EGL/egl_cache.cpp
index 3dc93ee..1b68344 100644
--- a/opengl/libs/EGL/egl_cache.cpp
+++ b/opengl/libs/EGL/egl_cache.cpp
@@ -38,8 +38,9 @@
 static const unsigned int kDeferredMonolithicSaveDelay = 4;
 
 // Multifile cache size limits
-constexpr uint32_t kMultifileHotCacheLimit = 8 * 1024 * 1024;
-constexpr uint32_t kMultifileCacheByteLimit = 32 * 1024 * 1024;
+constexpr uint32_t kMaxMultifileKeySize = 1 * 1024 * 1024;
+constexpr uint32_t kMaxMultifileValueSize = 8 * 1024 * 1024;
+constexpr uint32_t kMaxMultifileTotalSize = 32 * 1024 * 1024;
 
 namespace android {
 
@@ -250,7 +251,7 @@
     if (mMultifileMode) {
         mCacheByteLimit = static_cast<size_t>(
                 base::GetUintProperty<uint32_t>("ro.egl.blobcache.multifile_limit",
-                                                kMultifileCacheByteLimit));
+                                                kMaxMultifileTotalSize));
 
         // Check for a debug value
         int debugCacheSize = base::GetIntProperty("debug.egl.blobcache.multifile_limit", -1);
@@ -274,8 +275,9 @@
 
 MultifileBlobCache* egl_cache_t::getMultifileBlobCacheLocked() {
     if (mMultifileBlobCache == nullptr) {
-        mMultifileBlobCache.reset(
-                new MultifileBlobCache(mCacheByteLimit, kMultifileHotCacheLimit, mFilename));
+        mMultifileBlobCache.reset(new MultifileBlobCache(kMaxMultifileKeySize,
+                                                         kMaxMultifileValueSize, mCacheByteLimit,
+                                                         mFilename));
     }
     return mMultifileBlobCache.get();
 }
diff --git a/opengl/libs/EGL/egl_display.cpp b/opengl/libs/EGL/egl_display.cpp
index 0b755aa..525fed1 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
@@ -343,8 +353,9 @@
             // Typically that means there is an HDR capable display attached, but could be
             // support for attaching an HDR display. In either case, advertise support for
             // HDR color spaces.
-            mExtensionString.append(
-                    "EGL_EXT_gl_colorspace_bt2020_linear EGL_EXT_gl_colorspace_bt2020_pq ");
+            mExtensionString.append("EGL_EXT_gl_colorspace_bt2020_hlg "
+                                    "EGL_EXT_gl_colorspace_bt2020_linear "
+                                    "EGL_EXT_gl_colorspace_bt2020_pq ");
         }
 
         char const* start = gExtensionString;
@@ -361,6 +372,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..48718bb 100644
--- a/opengl/libs/EGL/egl_platform_entries.cpp
+++ b/opengl/libs/EGL/egl_platform_entries.cpp
@@ -18,6 +18,7 @@
 
 #include "egl_platform_entries.h"
 
+#include <aidl/android/hardware/graphics/common/PixelFormat.h>
 #include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <android/hardware_buffer.h>
@@ -29,7 +30,6 @@
 #include <private/android/AHardwareBufferHelpers.h>
 #include <stdlib.h>
 #include <string.h>
-#include <aidl/android/hardware/graphics/common/PixelFormat.h>
 
 #include <condition_variable>
 #include <deque>
@@ -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 "
@@ -420,11 +421,14 @@
         return HAL_DATASPACE_V0_SCRGB;
     } else if (colorspace == EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT) {
         return HAL_DATASPACE_V0_SCRGB_LINEAR;
+    } else if (colorspace == EGL_GL_COLORSPACE_BT2020_HLG_EXT) {
+        return static_cast<android_dataspace>(HAL_DATASPACE_BT2020_HLG);
     } else if (colorspace == EGL_GL_COLORSPACE_BT2020_LINEAR_EXT) {
         return HAL_DATASPACE_BT2020_LINEAR;
     } else if (colorspace == EGL_GL_COLORSPACE_BT2020_PQ_EXT) {
         return HAL_DATASPACE_BT2020_PQ;
     }
+
     return HAL_DATASPACE_UNKNOWN;
 }
 
@@ -451,6 +455,9 @@
     if (findExtension(dp->disp.queryString.extensions, "EGL_EXT_gl_colorspace_scrgb_linear")) {
         colorSpaces.push_back(EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT);
     }
+    if (findExtension(dp->disp.queryString.extensions, "EGL_EXT_gl_colorspace_bt2020_hlg")) {
+        colorSpaces.push_back(EGL_GL_COLORSPACE_BT2020_HLG_EXT);
+    }
     if (findExtension(dp->disp.queryString.extensions, "EGL_EXT_gl_colorspace_bt2020_linear")) {
         colorSpaces.push_back(EGL_GL_COLORSPACE_BT2020_LINEAR_EXT);
     }
@@ -484,6 +491,7 @@
                 case EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT:
                 case EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT:
                 case EGL_GL_COLORSPACE_SCRGB_EXT:
+                case EGL_GL_COLORSPACE_BT2020_HLG_EXT:
                 case EGL_GL_COLORSPACE_BT2020_LINEAR_EXT:
                 case EGL_GL_COLORSPACE_BT2020_PQ_EXT:
                 case EGL_GL_COLORSPACE_DISPLAY_P3_LINEAR_EXT:
@@ -2185,6 +2193,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 +2230,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 +2288,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 +2322,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 +2411,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/opengl/libs/EGL/fuzzer/Android.bp b/opengl/libs/EGL/fuzzer/Android.bp
new file mode 100644
index 0000000..022a2a3
--- /dev/null
+++ b/opengl/libs/EGL/fuzzer/Android.bp
@@ -0,0 +1,42 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_fuzz {
+    name: "MultifileBlobCache_fuzzer",
+
+    fuzz_config: {
+        cc: ["cnorthrop@google.com"],
+        libfuzzer_options: ["len_control=0"],
+    },
+
+    static_libs: [
+        "libbase",
+        "libEGL_blobCache",
+        "liblog",
+        "libutils",
+    ],
+
+    srcs: [
+        "MultifileBlobCache_fuzzer.cpp",
+    ],
+}
diff --git a/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp b/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp
new file mode 100644
index 0000000..633cc9c
--- /dev/null
+++ b/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp
@@ -0,0 +1,158 @@
+/*
+ ** Copyright 2023, The Android Open Source Project
+ **
+ ** Licensed under the Apache License, Version 2.0 (the "License");
+ ** you may not use this file except in compliance with the License.
+ ** You may obtain a copy of the License at
+ **
+ **     http://www.apache.org/licenses/LICENSE-2.0
+ **
+ ** Unless required by applicable law or agreed to in writing, software
+ ** distributed under the License is distributed on an "AS IS" BASIS,
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License.
+ */
+
+#include "MultifileBlobCache.h"
+
+#include <android-base/test_utils.h>
+#include <fcntl.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+namespace android {
+
+constexpr size_t kMaxKeySize = 2 * 1024;
+constexpr size_t kMaxValueSize = 6 * 1024;
+constexpr size_t kMaxTotalSize = 32 * 1024;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    // To fuzz this, we're going to create a key/value pair from data
+    // and use them with MultifileBlobCache in a random order
+    // - Use the first entry in data to determine keySize
+    // - Use the second entry in data to determine valueSize
+    // - Mod each of them against half the remaining size, ensuring both fit
+    // - Create key and value using sizes from data
+    // - Use remaining data to switch between GET and SET while
+    //   tweaking the keys slightly
+    // - Ensure two cache cleaning scenarios are hit at the end
+
+    // Ensure we have enough data to create interesting key/value pairs
+    size_t kMinInputLength = 128;
+    if (size < kMinInputLength) {
+        return 0;
+    }
+
+    // Need non-zero sizes for interesting results
+    if (data[0] == 0 || data[1] == 0) {
+        return 0;
+    }
+
+    // We need to divide the data up into buffers and sizes
+    FuzzedDataProvider fdp(data, size);
+
+    // Pull two values from data for key and value size
+    EGLsizeiANDROID keySize = static_cast<EGLsizeiANDROID>(fdp.ConsumeIntegral<uint8_t>());
+    EGLsizeiANDROID valueSize = static_cast<EGLsizeiANDROID>(fdp.ConsumeIntegral<uint8_t>());
+    size -= 2 * sizeof(uint8_t);
+
+    // Ensure key and value fit in the remaining space (cap them at half data size)
+    keySize = keySize % (size >> 1);
+    valueSize = valueSize % (size >> 1);
+
+    // If either size ended up zero, just move on to save time
+    if (keySize == 0 || valueSize == 0) {
+        return 0;
+    }
+
+    // Create key and value from remaining data
+    std::vector<uint8_t> key;
+    std::vector<uint8_t> value;
+    key = fdp.ConsumeBytes<uint8_t>(keySize);
+    value = fdp.ConsumeBytes<uint8_t>(valueSize);
+
+    // Create a tempfile and a cache
+    std::unique_ptr<TemporaryFile> tempFile;
+    std::unique_ptr<MultifileBlobCache> mbc;
+
+    tempFile.reset(new TemporaryFile());
+    mbc.reset(
+            new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &tempFile->path[0]));
+    // With remaining data, select different paths below
+    int loopCount = 1;
+    uint8_t bumpCount = 0;
+    while (fdp.remaining_bytes() > 0) {
+        // Bounce back and forth between gets and sets
+        if (fdp.ConsumeBool()) {
+            mbc->set(key.data(), keySize, value.data(), valueSize);
+        } else {
+            uint8_t* buffer = new uint8_t[valueSize];
+            mbc->get(key.data(), keySize, buffer, valueSize);
+            delete[] buffer;
+        }
+
+        // Bump the key and values periodically, causing different hits/misses
+        if (fdp.ConsumeBool()) {
+            key[0]++;
+            value[0]++;
+            bumpCount++;
+        }
+
+        // Reset the key and value periodically to hit old entries
+        if (fdp.ConsumeBool()) {
+            key[0] -= bumpCount;
+            value[0] -= bumpCount;
+            bumpCount = 0;
+        }
+
+        loopCount++;
+    }
+    mbc->finish();
+
+    // Fill 2 keys and 2 values to max size with unique values
+    std::vector<uint8_t> maxKey1, maxKey2, maxValue1, maxValue2;
+    maxKey1.resize(kMaxKeySize, 0);
+    maxKey2.resize(kMaxKeySize, 0);
+    maxValue1.resize(kMaxValueSize, 0);
+    maxValue2.resize(kMaxValueSize, 0);
+    for (int i = 0; i < keySize && i < kMaxKeySize; ++i) {
+        maxKey1[i] = key[i];
+        maxKey2[i] = key[i] - 1;
+    }
+    for (int i = 0; i < valueSize && i < kMaxValueSize; ++i) {
+        maxValue1[i] = value[i];
+        maxValue2[i] = value[i] - 1;
+    }
+
+    // Trigger hot cache trimming
+    // Place the maxKey/maxValue twice
+    // The first will fit, the second will trigger hot cache trimming
+    tempFile.reset(new TemporaryFile());
+    mbc.reset(
+            new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &tempFile->path[0]));
+    uint8_t* buffer = new uint8_t[kMaxValueSize];
+    mbc->set(maxKey1.data(), kMaxKeySize, maxValue1.data(), kMaxValueSize);
+    mbc->set(maxKey2.data(), kMaxKeySize, maxValue2.data(), kMaxValueSize);
+    mbc->get(maxKey1.data(), kMaxKeySize, buffer, kMaxValueSize);
+    mbc->finish();
+
+    // Trigger cold cache trimming
+    // Create a total size small enough only one entry fits
+    // Since the cache will add a header, 2 * key + value will only hold one value, the second will
+    // overflow
+    tempFile.reset(new TemporaryFile());
+    mbc.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, 2 * (kMaxKeySize + kMaxValueSize),
+                                     &tempFile->path[0]));
+    mbc->set(maxKey1.data(), kMaxKeySize, maxValue1.data(), kMaxValueSize);
+    mbc->set(maxKey2.data(), kMaxKeySize, maxValue2.data(), kMaxValueSize);
+    mbc->get(maxKey1.data(), kMaxKeySize, buffer, kMaxValueSize);
+    mbc->finish();
+
+    delete[] buffer;
+    return 0;
+}
+
+} // namespace android
diff --git a/opengl/tests/EGLTest/egl_cache_test.cpp b/opengl/tests/EGLTest/egl_cache_test.cpp
index 2b3e3a4..f81c68f 100644
--- a/opengl/tests/EGLTest/egl_cache_test.cpp
+++ b/opengl/tests/EGLTest/egl_cache_test.cpp
@@ -15,7 +15,7 @@
  */
 
 #define LOG_TAG "EGL_test"
-//#define LOG_NDEBUG 0
+// #define LOG_NDEBUG 0
 
 #include <gtest/gtest.h>
 
@@ -27,6 +27,7 @@
 #include "MultifileBlobCache.h"
 #include "egl_display.h"
 
+#include <fstream>
 #include <memory>
 
 using namespace std::literals;
@@ -144,7 +145,7 @@
     return cachefileName;
 }
 
-TEST_P(EGLCacheTest, ModifiedCacheMisses) {
+TEST_P(EGLCacheTest, ModifiedCacheBeginMisses) {
     // Skip if not in multifile mode
     if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) {
         GTEST_SKIP() << "Skipping test designed for multifile";
@@ -168,11 +169,12 @@
     ASSERT_TRUE(cachefileName.length() > 0);
 
     // Stomp on the beginning of the cache file, breaking the key match
-    const long stomp = 0xbadf00d;
-    FILE *file = fopen(cachefileName.c_str(), "w");
-    fprintf(file, "%ld", stomp);
-    fflush(file);
-    fclose(file);
+    const char* stomp = "BADF00D";
+    std::fstream fs(cachefileName);
+    fs.seekp(0, std::ios_base::beg);
+    fs.write(stomp, strlen(stomp));
+    fs.flush();
+    fs.close();
 
     // Ensure no cache hit
     mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
@@ -185,6 +187,56 @@
     ASSERT_EQ(0xee, buf2[3]);
 }
 
+TEST_P(EGLCacheTest, ModifiedCacheEndMisses) {
+    // Skip if not in multifile mode
+    if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) {
+        GTEST_SKIP() << "Skipping test designed for multifile";
+    }
+
+    uint8_t buf[16] = { 0xee, 0xee, 0xee, 0xee,
+                        0xee, 0xee, 0xee, 0xee,
+                        0xee, 0xee, 0xee, 0xee,
+                        0xee, 0xee, 0xee, 0xee };
+
+    mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
+
+    mCache->setBlob("abcdefghij", 10, "klmnopqrstuvwxyz", 16);
+    ASSERT_EQ(16, mCache->getBlob("abcdefghij", 10, buf, 16));
+    ASSERT_EQ('w', buf[12]);
+    ASSERT_EQ('x', buf[13]);
+    ASSERT_EQ('y', buf[14]);
+    ASSERT_EQ('z', buf[15]);
+
+    // Ensure the cache file is written to disk
+    mCache->terminate();
+
+    // Depending on the cache mode, the file will be in different locations
+    std::string cachefileName = getCachefileName();
+    ASSERT_TRUE(cachefileName.length() > 0);
+
+    // Stomp on the END of the cache file, modifying its contents
+    const char* stomp = "BADF00D";
+    std::fstream fs(cachefileName);
+    fs.seekp(-strlen(stomp), std::ios_base::end);
+    fs.write(stomp, strlen(stomp));
+    fs.flush();
+    fs.close();
+
+    // Ensure no cache hit
+    mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
+    uint8_t buf2[16] = { 0xee, 0xee, 0xee, 0xee,
+                         0xee, 0xee, 0xee, 0xee,
+                         0xee, 0xee, 0xee, 0xee,
+                         0xee, 0xee, 0xee, 0xee };
+
+    // getBlob may return junk for required size, but should not return a cache hit
+    mCache->getBlob("abcdefghij", 10, buf2, 16);
+    ASSERT_EQ(0xee, buf2[0]);
+    ASSERT_EQ(0xee, buf2[1]);
+    ASSERT_EQ(0xee, buf2[2]);
+    ASSERT_EQ(0xee, buf2[3]);
+}
+
 TEST_P(EGLCacheTest, TerminatedCacheBelowCacheLimit) {
     uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
     mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
@@ -213,11 +265,68 @@
     // Cache should contain both the key and the value
     // So 8 bytes per entry, at least 24 bytes
     ASSERT_GE(mCache->getCacheSize(), 24);
-    mCache->setCacheLimit(4);
+
+    // Set the new limit and initialize cache
     mCache->terminate();
+    mCache->setCacheLimit(4);
+    mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
+
+    // Ensure the new limit is respected
     ASSERT_LE(mCache->getCacheSize(), 4);
 }
 
+TEST_P(EGLCacheTest, TrimCacheOnOverflow) {
+    // Skip if not in multifile mode
+    if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) {
+        GTEST_SKIP() << "Skipping test designed for multifile";
+    }
+
+    uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
+    mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
+
+    // Set one value in the cache
+    mCache->setBlob("abcd", 4, "efgh", 4);
+    ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4));
+    ASSERT_EQ('e', buf[0]);
+    ASSERT_EQ('f', buf[1]);
+    ASSERT_EQ('g', buf[2]);
+    ASSERT_EQ('h', buf[3]);
+
+    // Get the size of cache with a single entry
+    size_t cacheEntrySize = mCache->getCacheSize();
+
+    // Now reinitialize the cache, using max size equal to a single entry
+    mCache->terminate();
+    mCache->setCacheLimit(cacheEntrySize);
+    mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
+
+    // Ensure our cache still has original value
+    ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4));
+    ASSERT_EQ('e', buf[0]);
+    ASSERT_EQ('f', buf[1]);
+    ASSERT_EQ('g', buf[2]);
+    ASSERT_EQ('h', buf[3]);
+
+    // Set another value, which should overflow the cache and trim
+    mCache->setBlob("ijkl", 4, "mnop", 4);
+    ASSERT_EQ(4, mCache->getBlob("ijkl", 4, buf, 4));
+    ASSERT_EQ('m', buf[0]);
+    ASSERT_EQ('n', buf[1]);
+    ASSERT_EQ('o', buf[2]);
+    ASSERT_EQ('p', buf[3]);
+
+    // The cache should still be under the limit
+    ASSERT_TRUE(mCache->getCacheSize() == cacheEntrySize);
+
+    // And no cache hit on trimmed entry
+    uint8_t buf2[4] = { 0xee, 0xee, 0xee, 0xee };
+    mCache->getBlob("abcd", 4, buf2, 4);
+    ASSERT_EQ(0xee, buf2[0]);
+    ASSERT_EQ(0xee, buf2[1]);
+    ASSERT_EQ(0xee, buf2[2]);
+    ASSERT_EQ(0xee, buf2[3]);
+}
+
 INSTANTIATE_TEST_CASE_P(MonolithicCacheTests,
         EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Monolithic));
 INSTANTIATE_TEST_CASE_P(MultifileCacheTests,
diff --git a/services/automotive/display/Android.bp b/services/automotive/display/Android.bp
index 614a78e..72bd292 100644
--- a/services/automotive/display/Android.bp
+++ b/services/automotive/display/Android.bp
@@ -53,6 +53,4 @@
     vintf_fragments: [
         "manifest_android.frameworks.automotive.display@1.0.xml",
     ],
-
-    system_ext_specific: true,
 }
diff --git a/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc b/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc
index ea1077a..5c7f344 100644
--- a/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc
+++ b/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc
@@ -1,4 +1,4 @@
-service automotive_display /system_ext/bin/android.frameworks.automotive.display@1.0-service
+service automotive_display /system/bin/android.frameworks.automotive.display@1.0-service
     class hal
     user graphics
     group automotive_evs
diff --git a/services/gpuservice/GpuService.cpp b/services/gpuservice/GpuService.cpp
index aaa8c18..5e7b2e8 100644
--- a/services/gpuservice/GpuService.cpp
+++ b/services/gpuservice/GpuService.cpp
@@ -19,6 +19,7 @@
 #include "GpuService.h"
 
 #include <android-base/stringprintf.h>
+#include <android-base/properties.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IResultReceiver.h>
 #include <binder/Parcel.h>
@@ -46,6 +47,8 @@
 } // namespace
 
 const String16 sDump("android.permission.DUMP");
+const String16 sAccessGpuServicePermission("android.permission.ACCESS_GPU_SERVICE");
+const std::string sAngleGlesDriverSuffix = "angle";
 
 const char* const GpuService::SERVICE_NAME = "gpu";
 
@@ -88,6 +91,29 @@
     mGpuStats->insertTargetStatsArray(appPackageName, driverVersionCode, stats, values, valueCount);
 }
 
+void GpuService::toggleAngleAsSystemDriver(bool enabled) {
+    IPCThreadState* ipc = IPCThreadState::self();
+    const int pid = ipc->getCallingPid();
+    const int uid = ipc->getCallingUid();
+
+    // only system_server with the ACCESS_GPU_SERVICE permission is allowed to set
+    // persist.graphics.egl
+    if (uid != AID_SYSTEM ||
+        !PermissionCache::checkPermission(sAccessGpuServicePermission, pid, uid)) {
+        ALOGE("Permission Denial: can't set persist.graphics.egl from setAngleAsSystemDriver() "
+                "pid=%d, uid=%d\n", pid, uid);
+        return;
+    }
+
+    std::lock_guard<std::mutex> lock(mLock);
+    if (enabled) {
+        android::base::SetProperty("persist.graphics.egl", sAngleGlesDriverSuffix);
+    } else {
+        android::base::SetProperty("persist.graphics.egl", "");
+    }
+}
+
+
 void GpuService::setUpdatableDriverPath(const std::string& driverPath) {
     IPCThreadState* ipc = IPCThreadState::self();
     const int pid = ipc->getCallingPid();
diff --git a/services/gpuservice/GpuService.h b/services/gpuservice/GpuService.h
index e7e0cba..0e559f2 100644
--- a/services/gpuservice/GpuService.h
+++ b/services/gpuservice/GpuService.h
@@ -61,6 +61,7 @@
                         const uint64_t* values, const uint32_t valueCount) override;
     void setUpdatableDriverPath(const std::string& driverPath) override;
     std::string getUpdatableDriverPath() override;
+    void toggleAngleAsSystemDriver(bool enabled) override;
 
     /*
      * IBinder interface
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/Android.bp b/services/gpuservice/vts/Android.bp
index 83a40e7..a24822a 100644
--- a/services/gpuservice/vts/Android.bp
+++ b/services/gpuservice/vts/Android.bp
@@ -21,7 +21,7 @@
     srcs: ["src/**/*.java"],
     libs: [
         "tradefed",
-        "vts-core-tradefed-harness",
+        "compatibility-host-util",
     ],
     test_suites: [
         "general-tests",
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/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java b/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java
index 9fa9016..6c16335 100644
--- a/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java
+++ b/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java
@@ -19,11 +19,16 @@
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
+import android.platform.test.annotations.RestrictedBuildTest;
+
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
 
+import com.android.compatibility.common.util.PropertyUtil;
+import com.android.compatibility.common.util.VsrTest;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -57,15 +62,23 @@
                 commandResult.getStatus(), CommandStatus.SUCCESS);
     }
 
+    @VsrTest(requirements={"VSR-3.3-004"})
+    @RestrictedBuildTest
     @Test
     public void testGpuWorkPeriodTracepointFormat() throws Exception {
         CommandResult commandResult = getDevice().executeShellV2Command(
                 String.format("cat %s", GPU_WORK_PERIOD_TRACEPOINT_FORMAT_PATH));
 
         // If we failed to cat the tracepoint format then the test ends here.
-        assumeTrue(String.format("Failed to cat the gpu_work_period tracepoint format at %s",
-                        GPU_WORK_PERIOD_TRACEPOINT_FORMAT_PATH),
-                commandResult.getStatus().equals(CommandStatus.SUCCESS));
+        if (!commandResult.getStatus().equals(CommandStatus.SUCCESS)) {
+            String message = String.format(
+                "Failed to cat the gpu_work_period tracepoint format at %s\n",
+                GPU_WORK_PERIOD_TRACEPOINT_FORMAT_PATH);
+
+            // Tracepoint MUST exist on devices released with Android 14 or later
+            assumeTrue(message, PropertyUtil.getVsrApiLevel(getDevice()) >= 34);
+            fail(message);
+        }
 
         // Otherwise, we check that the fields match the expected fields.
         String actualFields = Arrays.stream(
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index b885435..69df45b 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -213,6 +213,7 @@
     name: "checkinput",
     required: [
         // native targets
+        "libgui_test",
         "libinput",
         "libinputflinger",
         "inputflinger_tests",
@@ -223,6 +224,9 @@
         "libinputservice_test",
         "Bug-115739809",
         "StructLayout_test",
+        // currently unused, but still must build correctly
+        "inputflinger",
+        "libinputflingerhost",
 
         // native fuzzers
         "inputflinger_latencytracker_fuzzer",
diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp
index 0c93f5c..2437d0f 100644
--- a/services/inputflinger/InputCommonConverter.cpp
+++ b/services/inputflinger/InputCommonConverter.cpp
@@ -200,17 +200,12 @@
     return static_cast<common::Button>(buttonState);
 }
 
-static common::ToolType getToolType(int32_t toolType) {
-    static_assert(static_cast<common::ToolType>(AMOTION_EVENT_TOOL_TYPE_UNKNOWN) ==
-                  common::ToolType::UNKNOWN);
-    static_assert(static_cast<common::ToolType>(AMOTION_EVENT_TOOL_TYPE_FINGER) ==
-                  common::ToolType::FINGER);
-    static_assert(static_cast<common::ToolType>(AMOTION_EVENT_TOOL_TYPE_STYLUS) ==
-                  common::ToolType::STYLUS);
-    static_assert(static_cast<common::ToolType>(AMOTION_EVENT_TOOL_TYPE_MOUSE) ==
-                  common::ToolType::MOUSE);
-    static_assert(static_cast<common::ToolType>(AMOTION_EVENT_TOOL_TYPE_ERASER) ==
-                  common::ToolType::ERASER);
+static common::ToolType getToolType(ToolType toolType) {
+    static_assert(static_cast<common::ToolType>(ToolType::UNKNOWN) == common::ToolType::UNKNOWN);
+    static_assert(static_cast<common::ToolType>(ToolType::FINGER) == common::ToolType::FINGER);
+    static_assert(static_cast<common::ToolType>(ToolType::STYLUS) == common::ToolType::STYLUS);
+    static_assert(static_cast<common::ToolType>(ToolType::MOUSE) == common::ToolType::MOUSE);
+    static_assert(static_cast<common::ToolType>(ToolType::ERASER) == common::ToolType::ERASER);
     return static_cast<common::ToolType>(toolType);
 }
 
diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp
index d33b298..aa55873 100644
--- a/services/inputflinger/InputListener.cpp
+++ b/services/inputflinger/InputListener.cpp
@@ -24,7 +24,6 @@
 
 #include <android-base/stringprintf.h>
 #include <android/log.h>
-#include <math.h>
 #include <utils/Trace.h>
 
 using android::base::StringPrintf;
@@ -47,16 +46,15 @@
 
 void InputListenerInterface::notify(const NotifyArgs& generalArgs) {
     Visitor v{
-            [&](const NotifyConfigurationChangedArgs& args) { notifyConfigurationChanged(&args); },
-            [&](const NotifyKeyArgs& args) { notifyKey(&args); },
-            [&](const NotifyMotionArgs& args) { notifyMotion(&args); },
-            [&](const NotifySwitchArgs& args) { notifySwitch(&args); },
-            [&](const NotifySensorArgs& args) { notifySensor(&args); },
-            [&](const NotifyVibratorStateArgs& args) { notifyVibratorState(&args); },
-            [&](const NotifyDeviceResetArgs& args) { notifyDeviceReset(&args); },
-            [&](const NotifyPointerCaptureChangedArgs& args) {
-                notifyPointerCaptureChanged(&args);
-            },
+            [&](const NotifyInputDevicesChangedArgs& args) { notifyInputDevicesChanged(args); },
+            [&](const NotifyConfigurationChangedArgs& args) { notifyConfigurationChanged(args); },
+            [&](const NotifyKeyArgs& args) { notifyKey(args); },
+            [&](const NotifyMotionArgs& args) { notifyMotion(args); },
+            [&](const NotifySwitchArgs& args) { notifySwitch(args); },
+            [&](const NotifySensorArgs& args) { notifySensor(args); },
+            [&](const NotifyVibratorStateArgs& args) { notifyVibratorState(args); },
+            [&](const NotifyDeviceResetArgs& args) { notifyDeviceReset(args); },
+            [&](const NotifyPointerCaptureChangedArgs& args) { notifyPointerCaptureChanged(args); },
     };
     std::visit(v, generalArgs);
 }
@@ -73,45 +71,49 @@
 QueuedInputListener::QueuedInputListener(InputListenerInterface& innerListener)
       : mInnerListener(innerListener) {}
 
-void QueuedInputListener::notifyConfigurationChanged(
-        const NotifyConfigurationChangedArgs* args) {
-    traceEvent(__func__, args->id);
-    mArgsQueue.emplace_back(*args);
+void QueuedInputListener::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
+    traceEvent(__func__, args.id);
+    mArgsQueue.emplace_back(args);
 }
 
-void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) {
-    traceEvent(__func__, args->id);
-    mArgsQueue.emplace_back(*args);
+void QueuedInputListener::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
+    traceEvent(__func__, args.id);
+    mArgsQueue.emplace_back(args);
 }
 
-void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) {
-    traceEvent(__func__, args->id);
-    mArgsQueue.emplace_back(*args);
+void QueuedInputListener::notifyKey(const NotifyKeyArgs& args) {
+    traceEvent(__func__, args.id);
+    mArgsQueue.emplace_back(args);
 }
 
-void QueuedInputListener::notifySwitch(const NotifySwitchArgs* args) {
-    traceEvent(__func__, args->id);
-    mArgsQueue.emplace_back(*args);
+void QueuedInputListener::notifyMotion(const NotifyMotionArgs& args) {
+    traceEvent(__func__, args.id);
+    mArgsQueue.emplace_back(args);
 }
 
-void QueuedInputListener::notifySensor(const NotifySensorArgs* args) {
-    traceEvent(__func__, args->id);
-    mArgsQueue.emplace_back(*args);
+void QueuedInputListener::notifySwitch(const NotifySwitchArgs& args) {
+    traceEvent(__func__, args.id);
+    mArgsQueue.emplace_back(args);
 }
 
-void QueuedInputListener::notifyVibratorState(const NotifyVibratorStateArgs* args) {
-    traceEvent(__func__, args->id);
-    mArgsQueue.emplace_back(*args);
+void QueuedInputListener::notifySensor(const NotifySensorArgs& args) {
+    traceEvent(__func__, args.id);
+    mArgsQueue.emplace_back(args);
 }
 
-void QueuedInputListener::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
-    traceEvent(__func__, args->id);
-    mArgsQueue.emplace_back(*args);
+void QueuedInputListener::notifyVibratorState(const NotifyVibratorStateArgs& args) {
+    traceEvent(__func__, args.id);
+    mArgsQueue.emplace_back(args);
 }
 
-void QueuedInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) {
-    traceEvent(__func__, args->id);
-    mArgsQueue.emplace_back(*args);
+void QueuedInputListener::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
+    traceEvent(__func__, args.id);
+    mArgsQueue.emplace_back(args);
+}
+
+void QueuedInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) {
+    traceEvent(__func__, args.id);
+    mArgsQueue.emplace_back(args);
 }
 
 void QueuedInputListener::flush() {
diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp
index 9182503..ddebcad 100644
--- a/services/inputflinger/InputManager.cpp
+++ b/services/inputflinger/InputManager.cpp
@@ -57,9 +57,8 @@
  * The event flow is via the "InputListener" interface, as follows:
  * InputReader -> UnwantedInteractionBlocker -> InputProcessor -> InputDispatcher
  */
-InputManager::InputManager(
-        const sp<InputReaderPolicyInterface>& readerPolicy,
-        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
+InputManager::InputManager(const sp<InputReaderPolicyInterface>& readerPolicy,
+                           InputDispatcherPolicyInterface& dispatcherPolicy) {
     mDispatcher = createInputDispatcher(dispatcherPolicy);
     mProcessor = std::make_unique<InputProcessor>(*mDispatcher);
     mBlocker = std::make_unique<UnwantedInteractionBlocker>(*mProcessor);
@@ -110,10 +109,6 @@
     return *mReader;
 }
 
-UnwantedInteractionBlockerInterface& InputManager::getBlocker() {
-    return *mBlocker;
-}
-
 InputProcessorInterface& InputManager::getProcessor() {
     return *mProcessor;
 }
@@ -129,6 +124,17 @@
     mDispatcher->monitor();
 }
 
+void InputManager::dump(std::string& dump) {
+    mReader->dump(dump);
+    dump += '\n';
+    mBlocker->dump(dump);
+    dump += '\n';
+    mProcessor->dump(dump);
+    dump += '\n';
+    mDispatcher->dump(dump);
+    dump += '\n';
+}
+
 // Used by tests only.
 binder::Status InputManager::createInputChannel(const std::string& name, InputChannel* outChannel) {
     IPCThreadState* ipc = IPCThreadState::self();
diff --git a/services/inputflinger/InputManager.h b/services/inputflinger/InputManager.h
index 1137193..b6ad419 100644
--- a/services/inputflinger/InputManager.h
+++ b/services/inputflinger/InputManager.h
@@ -82,9 +82,6 @@
     /* Gets the input reader. */
     virtual InputReaderInterface& getReader() = 0;
 
-    /* Gets the unwanted interaction blocker. */
-    virtual UnwantedInteractionBlockerInterface& getBlocker() = 0;
-
     /* Gets the input processor */
     virtual InputProcessorInterface& getProcessor() = 0;
 
@@ -93,6 +90,9 @@
 
     /* Check that the input stages have not deadlocked. */
     virtual void monitor() = 0;
+
+    /* Dump the state of the components controlled by the input manager. */
+    virtual void dump(std::string& dump) = 0;
 };
 
 class InputManager : public InputManagerInterface, public BnInputFlinger {
@@ -100,18 +100,17 @@
     ~InputManager() override;
 
 public:
-    InputManager(
-            const sp<InputReaderPolicyInterface>& readerPolicy,
-            const sp<InputDispatcherPolicyInterface>& dispatcherPolicy);
+    InputManager(const sp<InputReaderPolicyInterface>& readerPolicy,
+                 InputDispatcherPolicyInterface& dispatcherPolicy);
 
     status_t start() override;
     status_t stop() override;
 
     InputReaderInterface& getReader() override;
-    UnwantedInteractionBlockerInterface& getBlocker() override;
     InputProcessorInterface& getProcessor() override;
     InputDispatcherInterface& getDispatcher() override;
     void monitor() override;
+    void dump(std::string& dump) override;
 
     status_t dump(int fd, const Vector<String16>& args) override;
     binder::Status createInputChannel(const std::string& name, InputChannel* outChannel) override;
diff --git a/services/inputflinger/InputProcessor.cpp b/services/inputflinger/InputProcessor.cpp
index a98b383..7a84be9 100644
--- a/services/inputflinger/InputProcessor.cpp
+++ b/services/inputflinger/InputProcessor.cpp
@@ -413,63 +413,69 @@
     }
 }
 
-void InputProcessor::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) {
+void InputProcessor::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
+    // pass through
+    mQueuedListener.notify(args);
+    mQueuedListener.flush();
+}
+
+void InputProcessor::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
     // pass through
     mQueuedListener.notifyConfigurationChanged(args);
     mQueuedListener.flush();
 }
 
-void InputProcessor::notifyKey(const NotifyKeyArgs* args) {
+void InputProcessor::notifyKey(const NotifyKeyArgs& args) {
     // pass through
     mQueuedListener.notifyKey(args);
     mQueuedListener.flush();
 }
 
-void InputProcessor::notifyMotion(const NotifyMotionArgs* args) {
+void InputProcessor::notifyMotion(const NotifyMotionArgs& args) {
     { // acquire lock
         std::scoped_lock lock(mLock);
         // MotionClassifier is only used for touch events, for now
-        const bool sendToMotionClassifier = mMotionClassifier && isTouchEvent(*args);
+        const bool sendToMotionClassifier = mMotionClassifier && isTouchEvent(args);
         if (!sendToMotionClassifier) {
             mQueuedListener.notifyMotion(args);
         } else {
-            NotifyMotionArgs newArgs(*args);
+            NotifyMotionArgs newArgs(args);
             const MotionClassification newClassification = mMotionClassifier->classify(newArgs);
-            LOG_ALWAYS_FATAL_IF(args->classification != MotionClassification::NONE &&
+            LOG_ALWAYS_FATAL_IF(args.classification != MotionClassification::NONE &&
                                         newClassification != MotionClassification::NONE,
                                 "Conflicting classifications %s (new) and %s (old)!",
                                 motionClassificationToString(newClassification),
-                                motionClassificationToString(args->classification));
+                                motionClassificationToString(args.classification));
             newArgs.classification = newClassification;
-            mQueuedListener.notifyMotion(&newArgs);
+            mQueuedListener.notifyMotion(newArgs);
         }
     } // release lock
     mQueuedListener.flush();
 }
 
-void InputProcessor::notifySensor(const NotifySensorArgs* args) {
+void InputProcessor::notifySensor(const NotifySensorArgs& args) {
     // pass through
     mQueuedListener.notifySensor(args);
     mQueuedListener.flush();
 }
 
-void InputProcessor::notifyVibratorState(const NotifyVibratorStateArgs* args) {
+void InputProcessor::notifyVibratorState(const NotifyVibratorStateArgs& args) {
     // pass through
     mQueuedListener.notifyVibratorState(args);
     mQueuedListener.flush();
 }
 
-void InputProcessor::notifySwitch(const NotifySwitchArgs* args) {
+void InputProcessor::notifySwitch(const NotifySwitchArgs& args) {
     // pass through
     mQueuedListener.notifySwitch(args);
     mQueuedListener.flush();
 }
 
-void InputProcessor::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
+void InputProcessor::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
     { // acquire lock
         std::scoped_lock lock(mLock);
         if (mMotionClassifier) {
-            mMotionClassifier->reset(*args);
+            mMotionClassifier->reset(args);
         }
     } // release lock
 
@@ -478,7 +484,7 @@
     mQueuedListener.flush();
 }
 
-void InputProcessor::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) {
+void InputProcessor::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) {
     // pass through
     mQueuedListener.notifyPointerCaptureChanged(args);
     mQueuedListener.flush();
diff --git a/services/inputflinger/InputProcessor.h b/services/inputflinger/InputProcessor.h
index f4d02b6..dcbfebc 100644
--- a/services/inputflinger/InputProcessor.h
+++ b/services/inputflinger/InputProcessor.h
@@ -245,14 +245,15 @@
 public:
     explicit InputProcessor(InputListenerInterface& listener);
 
-    void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override;
-    void notifyKey(const NotifyKeyArgs* args) override;
-    void notifyMotion(const NotifyMotionArgs* args) override;
-    void notifySwitch(const NotifySwitchArgs* args) override;
-    void notifySensor(const NotifySensorArgs* args) override;
-    void notifyVibratorState(const NotifyVibratorStateArgs* args) override;
-    void notifyDeviceReset(const NotifyDeviceResetArgs* args) override;
-    void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override;
+    void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
+    void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
+    void notifyKey(const NotifyKeyArgs& args) override;
+    void notifyMotion(const NotifyMotionArgs& args) override;
+    void notifySwitch(const NotifySwitchArgs& args) override;
+    void notifySensor(const NotifySensorArgs& args) override;
+    void notifyVibratorState(const NotifyVibratorStateArgs& args) override;
+    void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
+    void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
 
     void dump(std::string& dump) override;
     void monitor() override;
diff --git a/services/inputflinger/InputReaderBase.cpp b/services/inputflinger/InputReaderBase.cpp
index 2450235..4ec5b89 100644
--- a/services/inputflinger/InputReaderBase.cpp
+++ b/services/inputflinger/InputReaderBase.cpp
@@ -38,50 +38,6 @@
 
 // --- InputReaderConfiguration ---
 
-std::string InputReaderConfiguration::changesToString(uint32_t changes) {
-    if (changes == 0) {
-        return "<none>";
-    }
-    std::string result;
-    if (changes & CHANGE_POINTER_SPEED) {
-        result += "POINTER_SPEED | ";
-    }
-    if (changes & CHANGE_POINTER_GESTURE_ENABLEMENT) {
-        result += "POINTER_GESTURE_ENABLEMENT | ";
-    }
-    if (changes & CHANGE_DISPLAY_INFO) {
-        result += "DISPLAY_INFO | ";
-    }
-    if (changes & CHANGE_SHOW_TOUCHES) {
-        result += "SHOW_TOUCHES | ";
-    }
-    if (changes & CHANGE_KEYBOARD_LAYOUTS) {
-        result += "KEYBOARD_LAYOUTS | ";
-    }
-    if (changes & CHANGE_DEVICE_ALIAS) {
-        result += "DEVICE_ALIAS | ";
-    }
-    if (changes & CHANGE_TOUCH_AFFINE_TRANSFORMATION) {
-        result += "TOUCH_AFFINE_TRANSFORMATION | ";
-    }
-    if (changes & CHANGE_EXTERNAL_STYLUS_PRESENCE) {
-        result += "EXTERNAL_STYLUS_PRESENCE | ";
-    }
-    if (changes & CHANGE_POINTER_CAPTURE) {
-        result += "POINTER_CAPTURE | ";
-    }
-    if (changes & CHANGE_ENABLED_STATE) {
-        result += "ENABLED_STATE | ";
-    }
-    if (changes & CHANGE_TOUCHPAD_SETTINGS) {
-        result += "TOUCHPAD_SETTINGS | ";
-    }
-    if (changes & CHANGE_MUST_REOPEN) {
-        result += "MUST_REOPEN | ";
-    }
-    return result;
-}
-
 std::optional<DisplayViewport> InputReaderConfiguration::getDisplayViewportByUniqueId(
         const std::string& uniqueDisplayId) const {
     if (uniqueDisplayId.empty()) {
diff --git a/services/inputflinger/NotifyArgs.cpp b/services/inputflinger/NotifyArgs.cpp
index b192ad7..408fbed 100644
--- a/services/inputflinger/NotifyArgs.cpp
+++ b/services/inputflinger/NotifyArgs.cpp
@@ -29,6 +29,12 @@
 
 namespace android {
 
+// --- NotifyInputDevicesChangedArgs ---
+
+NotifyInputDevicesChangedArgs::NotifyInputDevicesChangedArgs(int32_t id,
+                                                             std::vector<InputDeviceInfo> infos)
+      : id(id), inputDeviceInfos(std::move(infos)) {}
+
 // --- NotifyConfigurationChangedArgs ---
 
 NotifyConfigurationChangedArgs::NotifyConfigurationChangedArgs(int32_t id, nsecs_t eventTime)
@@ -161,9 +167,9 @@
                 StringPrintf("id=%" PRIu32 " x=%.1f y=%.1f pressure=%.1f", pointerProperties[i].id,
                              pointerCoords[i].getX(), pointerCoords[i].getY(),
                              pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
-        const int32_t toolType = pointerProperties[i].toolType;
-        if (toolType != AMOTION_EVENT_TOOL_TYPE_FINGER) {
-            coords += StringPrintf(" toolType=%s", motionToolTypeToString(toolType));
+        const ToolType toolType = pointerProperties[i].toolType;
+        if (toolType != ToolType::FINGER) {
+            coords += StringPrintf(" toolType=%s", ftl::enum_string(toolType).c_str());
         }
         const float major = pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
         const float minor = pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);
@@ -234,6 +240,7 @@
 
 const char* toString(const NotifyArgs& args) {
     Visitor toStringVisitor{
+            [&](const NotifyInputDevicesChangedArgs&) { return "NotifyInputDevicesChangedArgs"; },
             [&](const NotifyConfigurationChangedArgs&) { return "NotifyConfigurationChangedArgs"; },
             [&](const NotifyKeyArgs&) { return "NotifyKeyArgs"; },
             [&](const NotifyMotionArgs&) { return "NotifyMotionArgs"; },
diff --git a/services/inputflinger/PreferStylusOverTouchBlocker.cpp b/services/inputflinger/PreferStylusOverTouchBlocker.cpp
index ddd5146..fbd296c 100644
--- a/services/inputflinger/PreferStylusOverTouchBlocker.cpp
+++ b/services/inputflinger/PreferStylusOverTouchBlocker.cpp
@@ -24,11 +24,11 @@
     bool hasTouch = false;
     for (size_t i = 0; i < args.pointerCount; i++) {
         // Make sure we are canceling stylus pointers
-        const int32_t toolType = args.pointerProperties[i].toolType;
+        const ToolType toolType = args.pointerProperties[i].toolType;
         if (isStylusToolType(toolType)) {
             hasStylus = true;
         }
-        if (toolType == AMOTION_EVENT_TOOL_TYPE_FINGER) {
+        if (toolType == ToolType::FINGER) {
             hasTouch = true;
         }
     }
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index 495334e..f0b1072 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -27,6 +27,14 @@
       "name": "libinputservice_test"
     },
     {
+      "name": "libgui_test",
+      "options": [
+        {
+          "native-test-flag": "--gtest_filter=\"InputSurfacesTest*:MultiDisplayTests*\""
+        }
+      ]
+    },
+    {
       "name": "CtsHardwareTestCases",
       "options": [
         {
@@ -123,6 +131,14 @@
       "name": "libinputservice_test"
     },
     {
+      "name": "libgui_test",
+      "options": [
+        {
+          "native-test-flag": "--gtest_filter=\"InputSurfacesTest*:MultiDisplayTests*\""
+        }
+      ]
+    },
+    {
       "name": "CtsHardwareTestCases",
       "options": [
         {
diff --git a/services/inputflinger/UnwantedInteractionBlocker.cpp b/services/inputflinger/UnwantedInteractionBlocker.cpp
index c170b81..02bc47d 100644
--- a/services/inputflinger/UnwantedInteractionBlocker.cpp
+++ b/services/inputflinger/UnwantedInteractionBlocker.cpp
@@ -18,6 +18,7 @@
 #include "UnwantedInteractionBlocker.h"
 
 #include <android-base/stringprintf.h>
+#include <ftl/enum.h>
 #include <input/PrintTools.h>
 #include <inttypes.h>
 #include <linux/input-event-codes.h>
@@ -98,18 +99,21 @@
     return false;
 }
 
-static int getLinuxToolCode(int toolType) {
+static int getLinuxToolCode(ToolType toolType) {
     switch (toolType) {
-        case AMOTION_EVENT_TOOL_TYPE_STYLUS:
+        case ToolType::STYLUS:
             return BTN_TOOL_PEN;
-        case AMOTION_EVENT_TOOL_TYPE_ERASER:
+        case ToolType::ERASER:
             return BTN_TOOL_RUBBER;
-        case AMOTION_EVENT_TOOL_TYPE_FINGER:
+        case ToolType::FINGER:
             return BTN_TOOL_FINGER;
-        default:
-            ALOGW("Got tool type %" PRId32 ", converting to BTN_TOOL_FINGER", toolType);
-            return BTN_TOOL_FINGER;
+        case ToolType::UNKNOWN:
+        case ToolType::MOUSE:
+        case ToolType::PALM:
+            break;
     }
+    ALOGW("Got tool type %s, converting to BTN_TOOL_FINGER", ftl::enum_string(toolType).c_str());
+    return BTN_TOOL_FINGER;
 }
 
 static int32_t getActionUpForPointerId(const NotifyMotionArgs& args, int32_t pointerId) {
@@ -325,24 +329,24 @@
       : mQueuedListener(listener), mEnablePalmRejection(enablePalmRejection) {}
 
 void UnwantedInteractionBlocker::notifyConfigurationChanged(
-        const NotifyConfigurationChangedArgs* args) {
+        const NotifyConfigurationChangedArgs& args) {
     mQueuedListener.notifyConfigurationChanged(args);
     mQueuedListener.flush();
 }
 
-void UnwantedInteractionBlocker::notifyKey(const NotifyKeyArgs* args) {
+void UnwantedInteractionBlocker::notifyKey(const NotifyKeyArgs& args) {
     mQueuedListener.notifyKey(args);
     mQueuedListener.flush();
 }
 
-void UnwantedInteractionBlocker::notifyMotion(const NotifyMotionArgs* args) {
-    ALOGD_IF(DEBUG_INBOUND_MOTION, "%s: %s", __func__, args->dump().c_str());
+void UnwantedInteractionBlocker::notifyMotion(const NotifyMotionArgs& args) {
+    ALOGD_IF(DEBUG_INBOUND_MOTION, "%s: %s", __func__, args.dump().c_str());
     { // acquire lock
         std::scoped_lock lock(mLock);
         const std::vector<NotifyMotionArgs> processedArgs =
-                mPreferStylusOverTouchBlocker.processMotion(*args);
+                mPreferStylusOverTouchBlocker.processMotion(args);
         for (const NotifyMotionArgs& loopArgs : processedArgs) {
-            notifyMotionLocked(&loopArgs);
+            notifyMotionLocked(loopArgs);
         }
     } // release lock
 
@@ -352,61 +356,68 @@
 
 void UnwantedInteractionBlocker::enqueueOutboundMotionLocked(const NotifyMotionArgs& args) {
     ALOGD_IF(DEBUG_OUTBOUND_MOTION, "%s: %s", __func__, args.dump().c_str());
-    mQueuedListener.notifyMotion(&args);
+    mQueuedListener.notifyMotion(args);
 }
 
-void UnwantedInteractionBlocker::notifyMotionLocked(const NotifyMotionArgs* args) {
-    auto it = mPalmRejectors.find(args->deviceId);
-    const bool sendToPalmRejector = it != mPalmRejectors.end() && isFromTouchscreen(args->source);
+void UnwantedInteractionBlocker::notifyMotionLocked(const NotifyMotionArgs& args) {
+    auto it = mPalmRejectors.find(args.deviceId);
+    const bool sendToPalmRejector = it != mPalmRejectors.end() && isFromTouchscreen(args.source);
     if (!sendToPalmRejector) {
-        enqueueOutboundMotionLocked(*args);
+        enqueueOutboundMotionLocked(args);
         return;
     }
 
-    std::vector<NotifyMotionArgs> processedArgs = it->second.processMotion(*args);
+    std::vector<NotifyMotionArgs> processedArgs = it->second.processMotion(args);
     for (const NotifyMotionArgs& loopArgs : processedArgs) {
         enqueueOutboundMotionLocked(loopArgs);
     }
 }
 
-void UnwantedInteractionBlocker::notifySwitch(const NotifySwitchArgs* args) {
+void UnwantedInteractionBlocker::notifySwitch(const NotifySwitchArgs& args) {
     mQueuedListener.notifySwitch(args);
     mQueuedListener.flush();
 }
 
-void UnwantedInteractionBlocker::notifySensor(const NotifySensorArgs* args) {
+void UnwantedInteractionBlocker::notifySensor(const NotifySensorArgs& args) {
     mQueuedListener.notifySensor(args);
     mQueuedListener.flush();
 }
 
-void UnwantedInteractionBlocker::notifyVibratorState(const NotifyVibratorStateArgs* args) {
+void UnwantedInteractionBlocker::notifyVibratorState(const NotifyVibratorStateArgs& args) {
     mQueuedListener.notifyVibratorState(args);
     mQueuedListener.flush();
 }
-void UnwantedInteractionBlocker::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
+void UnwantedInteractionBlocker::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
     { // acquire lock
         std::scoped_lock lock(mLock);
-        auto it = mPalmRejectors.find(args->deviceId);
+        auto it = mPalmRejectors.find(args.deviceId);
         if (it != mPalmRejectors.end()) {
             AndroidPalmFilterDeviceInfo info = it->second.getPalmFilterDeviceInfo();
             // Re-create the object instead of resetting it
             mPalmRejectors.erase(it);
-            mPalmRejectors.emplace(args->deviceId, info);
+            mPalmRejectors.emplace(args.deviceId, info);
         }
         mQueuedListener.notifyDeviceReset(args);
-        mPreferStylusOverTouchBlocker.notifyDeviceReset(*args);
+        mPreferStylusOverTouchBlocker.notifyDeviceReset(args);
     } // release lock
     // Send events to the next stage without holding the lock
     mQueuedListener.flush();
 }
 
 void UnwantedInteractionBlocker::notifyPointerCaptureChanged(
-        const NotifyPointerCaptureChangedArgs* args) {
+        const NotifyPointerCaptureChangedArgs& args) {
     mQueuedListener.notifyPointerCaptureChanged(args);
     mQueuedListener.flush();
 }
 
 void UnwantedInteractionBlocker::notifyInputDevicesChanged(
+        const NotifyInputDevicesChangedArgs& args) {
+    onInputDevicesChanged(args.inputDeviceInfos);
+    mQueuedListener.notify(args);
+    mQueuedListener.flush();
+}
+
+void UnwantedInteractionBlocker::onInputDevicesChanged(
         const std::vector<InputDeviceInfo>& inputDevices) {
     std::scoped_lock lock(mLock);
     if (!mEnablePalmRejection) {
diff --git a/services/inputflinger/UnwantedInteractionBlocker.h b/services/inputflinger/UnwantedInteractionBlocker.h
index 5d0dde8..419da83 100644
--- a/services/inputflinger/UnwantedInteractionBlocker.h
+++ b/services/inputflinger/UnwantedInteractionBlocker.h
@@ -90,16 +90,16 @@
     explicit UnwantedInteractionBlocker(InputListenerInterface& listener);
     explicit UnwantedInteractionBlocker(InputListenerInterface& listener, bool enablePalmRejection);
 
-    void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override;
-    void notifyKey(const NotifyKeyArgs* args) override;
-    void notifyMotion(const NotifyMotionArgs* args) override;
-    void notifySwitch(const NotifySwitchArgs* args) override;
-    void notifySensor(const NotifySensorArgs* args) override;
-    void notifyVibratorState(const NotifyVibratorStateArgs* args) override;
-    void notifyDeviceReset(const NotifyDeviceResetArgs* args) override;
-    void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override;
+    void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
+    void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
+    void notifyKey(const NotifyKeyArgs& args) override;
+    void notifyMotion(const NotifyMotionArgs& args) override;
+    void notifySwitch(const NotifySwitchArgs& args) override;
+    void notifySensor(const NotifySensorArgs& args) override;
+    void notifyVibratorState(const NotifyVibratorStateArgs& args) override;
+    void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
+    void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
 
-    void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override;
     void dump(std::string& dump) override;
     void monitor() override;
 
@@ -119,10 +119,12 @@
     // Use a separate palm rejector for every touch device.
     std::map<int32_t /*deviceId*/, PalmRejector> mPalmRejectors GUARDED_BY(mLock);
     // TODO(b/210159205): delete this when simultaneous stylus and touch is supported
-    void notifyMotionLocked(const NotifyMotionArgs* args) REQUIRES(mLock);
+    void notifyMotionLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
 
     // Call this function for outbound events so that they can be logged when logging is enabled.
     void enqueueOutboundMotionLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
+
+    void onInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices);
 };
 
 class SlotState {
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index f03c837..f65533e 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -48,10 +48,8 @@
 
 class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
 public:
-    FakeInputDispatcherPolicy() {}
-
-protected:
-    virtual ~FakeInputDispatcherPolicy() {}
+    FakeInputDispatcherPolicy() = default;
+    virtual ~FakeInputDispatcherPolicy() = default;
 
 private:
     void notifyConfigurationChanged(nsecs_t) override {}
@@ -82,24 +80,23 @@
 
     void notifyVibratorState(int32_t deviceId, bool isOn) override {}
 
-    void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) override {
-        *outConfig = mConfig;
+    InputDispatcherConfiguration getDispatcherConfiguration() override { return mConfig; }
+
+    bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override {
+        return true; // dispatch event normally
     }
 
-    bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) override {
-        return true;
-    }
-
-    void interceptKeyBeforeQueueing(const KeyEvent*, uint32_t&) override {}
+    void interceptKeyBeforeQueueing(const KeyEvent&, uint32_t&) override {}
 
     void interceptMotionBeforeQueueing(int32_t, nsecs_t, uint32_t&) override {}
 
-    nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent*, uint32_t) override {
+    nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent&, uint32_t) override {
         return 0;
     }
 
-    bool dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent*, uint32_t, KeyEvent*) override {
-        return false;
+    std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent&,
+                                                 uint32_t) override {
+        return {};
     }
 
     void notifySwitch(nsecs_t, uint32_t, uint32_t, uint32_t) override {}
@@ -207,7 +204,7 @@
 
     pointerProperties[0].clear();
     pointerProperties[0].id = 0;
-    pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+    pointerProperties[0].toolType = ToolType::FINGER;
 
     pointerCoords[0].clear();
     pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 100);
@@ -235,7 +232,7 @@
 
     pointerProperties[0].clear();
     pointerProperties[0].id = 0;
-    pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+    pointerProperties[0].toolType = ToolType::FINGER;
 
     pointerCoords[0].clear();
     pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 100);
@@ -258,7 +255,7 @@
 
 static void benchmarkNotifyMotion(benchmark::State& state) {
     // Create dispatcher
-    sp<FakeInputDispatcherPolicy> fakePolicy = sp<FakeInputDispatcherPolicy>::make();
+    FakeInputDispatcherPolicy fakePolicy;
     InputDispatcher dispatcher(fakePolicy);
     dispatcher.setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
     dispatcher.start();
@@ -277,12 +274,12 @@
         motionArgs.action = AMOTION_EVENT_ACTION_DOWN;
         motionArgs.downTime = now();
         motionArgs.eventTime = motionArgs.downTime;
-        dispatcher.notifyMotion(&motionArgs);
+        dispatcher.notifyMotion(motionArgs);
 
         // Send ACTION_UP
         motionArgs.action = AMOTION_EVENT_ACTION_UP;
         motionArgs.eventTime = now();
-        dispatcher.notifyMotion(&motionArgs);
+        dispatcher.notifyMotion(motionArgs);
 
         window->consumeEvent();
         window->consumeEvent();
@@ -293,7 +290,7 @@
 
 static void benchmarkInjectMotion(benchmark::State& state) {
     // Create dispatcher
-    sp<FakeInputDispatcherPolicy> fakePolicy = sp<FakeInputDispatcherPolicy>::make();
+    FakeInputDispatcherPolicy fakePolicy;
     InputDispatcher dispatcher(fakePolicy);
     dispatcher.setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
     dispatcher.start();
@@ -327,7 +324,7 @@
 
 static void benchmarkOnWindowInfosChanged(benchmark::State& state) {
     // Create dispatcher
-    sp<FakeInputDispatcherPolicy> fakePolicy = sp<FakeInputDispatcherPolicy>::make();
+    FakeInputDispatcherPolicy fakePolicy;
     InputDispatcher dispatcher(fakePolicy);
     dispatcher.setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
     dispatcher.start();
@@ -343,8 +340,10 @@
     std::vector<gui::DisplayInfo> displayInfos{info};
 
     for (auto _ : state) {
-        dispatcher.onWindowInfosChanged(windowInfos, displayInfos);
-        dispatcher.onWindowInfosChanged(/*windowInfos=*/{}, /*displayInfos=*/{});
+        dispatcher.onWindowInfosChanged(
+                {windowInfos, displayInfos, /*vsyncId=*/0, /*timestamp=*/0});
+        dispatcher.onWindowInfosChanged(
+                {/*windowInfos=*/{}, /*displayInfos=*/{}, /*vsyncId=*/{}, /*timestamp=*/0});
     }
     dispatcher.stop();
 }
diff --git a/services/inputflinger/dispatcher/Connection.cpp b/services/inputflinger/dispatcher/Connection.cpp
index b4497fd..ed95de7 100644
--- a/services/inputflinger/dispatcher/Connection.cpp
+++ b/services/inputflinger/dispatcher/Connection.cpp
@@ -28,8 +28,6 @@
         inputPublisher(inputChannel),
         inputState(idGenerator) {}
 
-Connection::~Connection() {}
-
 const std::string Connection::getWindowName() const {
     if (inputChannel != nullptr) {
         return inputChannel->getName();
diff --git a/services/inputflinger/dispatcher/Connection.h b/services/inputflinger/dispatcher/Connection.h
index 6040e9b..2929d61 100644
--- a/services/inputflinger/dispatcher/Connection.h
+++ b/services/inputflinger/dispatcher/Connection.h
@@ -27,10 +27,7 @@
 struct DispatchEntry;
 
 /* Manages the dispatch state associated with a single input channel. */
-class Connection : public RefBase {
-protected:
-    virtual ~Connection();
-
+class Connection {
 public:
     enum class Status {
         // Everything is peachy.
diff --git a/services/inputflinger/dispatcher/FocusResolver.cpp b/services/inputflinger/dispatcher/FocusResolver.cpp
index 4da846b..0e4e79e 100644
--- a/services/inputflinger/dispatcher/FocusResolver.cpp
+++ b/services/inputflinger/dispatcher/FocusResolver.cpp
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include <optional>
 #define LOG_TAG "InputDispatcher"
 #define ATRACE_TAG ATRACE_TAG_INPUT
 
@@ -25,6 +26,7 @@
 #include <binder/Binder.h>
 #include <ftl/enum.h>
 #include <gui/WindowInfo.h>
+#include <unordered_set>
 
 #include "DebugConfig.h"
 #include "FocusResolver.h"
@@ -34,6 +36,11 @@
 
 namespace android::inputdispatcher {
 
+template <typename T>
+struct SpHash {
+    size_t operator()(const sp<T>& k) const { return std::hash<T*>()(k.get()); }
+};
+
 sp<IBinder> FocusResolver::getFocusedWindowToken(int32_t displayId) const {
     auto it = mFocusedWindowTokenByDisplay.find(displayId);
     return it != mFocusedWindowTokenByDisplay.end() ? it->second.second : nullptr;
@@ -54,30 +61,30 @@
         int32_t displayId, const std::vector<sp<WindowInfoHandle>>& windows) {
     std::string removeFocusReason;
 
-    // Check if the currently focused window is still focusable.
-    const sp<IBinder> currentFocus = getFocusedWindowToken(displayId);
-    if (currentFocus) {
-        Focusability result = isTokenFocusable(currentFocus, windows);
-        if (result == Focusability::OK) {
-            return std::nullopt;
-        }
-        removeFocusReason = ftl::enum_string(result);
-    }
-
-    // We don't have a focused window or the currently focused window is no longer focusable. Check
-    // to see if we can grant focus to the window that previously requested focus.
     const std::optional<FocusRequest> request = getFocusRequest(displayId);
+    const sp<IBinder> currentFocus = getFocusedWindowToken(displayId);
+
+    // Find the next focused token based on the latest FocusRequest. If the requested focus window
+    // cannot be focused, focus will be removed.
     if (request) {
         sp<IBinder> requestedFocus = request->token;
-        const Focusability result = isTokenFocusable(requestedFocus, windows);
+        sp<WindowInfoHandle> resolvedFocusWindow;
+        Focusability result = getResolvedFocusWindow(requestedFocus, windows, resolvedFocusWindow);
+        if (result == Focusability::OK && resolvedFocusWindow->getToken() == currentFocus) {
+            return std::nullopt;
+        }
         const Focusability previousResult = mLastFocusResultByDisplay[displayId];
         mLastFocusResultByDisplay[displayId] = result;
         if (result == Focusability::OK) {
+            LOG_ALWAYS_FATAL_IF(!resolvedFocusWindow,
+                                "Focused window should be non-null when result is OK!");
             return updateFocusedWindow(displayId,
                                        "Window became focusable. Previous reason: " +
                                                ftl::enum_string(previousResult),
-                                       requestedFocus, request->windowName);
+                                       resolvedFocusWindow->getToken(),
+                                       resolvedFocusWindow->getName());
         }
+        removeFocusReason = ftl::enum_string(result);
     }
 
     // Focused window is no longer focusable and we don't have a suitable focus request to grant.
@@ -96,35 +103,18 @@
         return std::nullopt;
     }
 
-    // Handle conditional focus requests, i.e. requests that have a focused token. These requests
-    // are not persistent. If the window is no longer focusable, we expect focus to go back to the
-    // previously focused window.
-    if (request.focusedToken) {
-        if (currentFocus != request.focusedToken) {
-            ALOGW("setFocusedWindow %s on display %" PRId32
-                  " ignored, reason: focusedToken %s is not focused",
-                  request.windowName.c_str(), displayId, request.focusedWindowName.c_str());
-            return std::nullopt;
-        }
-        Focusability result = isTokenFocusable(request.token, windows);
-        if (result == Focusability::OK) {
-            return updateFocusedWindow(displayId, "setFocusedWindow with focus check",
-                                       request.token, request.windowName);
-        }
-        ALOGW("setFocusedWindow %s on display %" PRId32 " ignored, reason: %s",
-              request.windowName.c_str(), displayId, ftl::enum_string(result).c_str());
-        return std::nullopt;
-    }
-
-    Focusability result = isTokenFocusable(request.token, windows);
+    sp<WindowInfoHandle> resolvedFocusWindow;
+    Focusability result = getResolvedFocusWindow(request.token, windows, resolvedFocusWindow);
     // Update focus request. The focus resolver will always try to handle this request if there is
     // no focused window on the display.
     mFocusRequestByDisplay[displayId] = request;
     mLastFocusResultByDisplay[displayId] = result;
 
     if (result == Focusability::OK) {
-        return updateFocusedWindow(displayId, "setFocusedWindow", request.token,
-                                   request.windowName);
+        LOG_ALWAYS_FATAL_IF(!resolvedFocusWindow,
+                            "Focused window should be non-null when result is OK!");
+        return updateFocusedWindow(displayId, "setFocusedWindow", resolvedFocusWindow->getToken(),
+                                   resolvedFocusWindow->getName());
     }
 
     // The requested window is not currently focusable. Wait for the window to become focusable
@@ -134,11 +124,43 @@
                                nullptr);
 }
 
+FocusResolver::Focusability FocusResolver::getResolvedFocusWindow(
+        const sp<IBinder>& token, const std::vector<sp<WindowInfoHandle>>& windows,
+        sp<WindowInfoHandle>& outFocusableWindow) {
+    sp<IBinder> curFocusCandidate = token;
+    bool focusedWindowFound = false;
+
+    // Keep track of all windows reached to prevent a cyclical transferFocus request.
+    std::unordered_set<sp<IBinder>, SpHash<IBinder>> tokensReached;
+
+    while (curFocusCandidate != nullptr && tokensReached.count(curFocusCandidate) == 0) {
+        tokensReached.emplace(curFocusCandidate);
+        Focusability result = isTokenFocusable(curFocusCandidate, windows, outFocusableWindow);
+        if (result == Focusability::OK) {
+            LOG_ALWAYS_FATAL_IF(!outFocusableWindow,
+                                "Focused window should be non-null when result is OK!");
+            focusedWindowFound = true;
+            // outFocusableWindow has been updated by isTokenFocusable to contain
+            // the window info for curFocusCandidate. See if we can grant focus
+            // to the token that it wants to transfer its focus to.
+            curFocusCandidate = outFocusableWindow->getInfo()->focusTransferTarget;
+        }
+
+        // If the initial token is not focusable, return early with the failed result.
+        if (!focusedWindowFound) {
+            return result;
+        }
+    }
+
+    return focusedWindowFound ? Focusability::OK : Focusability::NO_WINDOW;
+}
+
 FocusResolver::Focusability FocusResolver::isTokenFocusable(
-        const sp<IBinder>& token, const std::vector<sp<WindowInfoHandle>>& windows) {
+        const sp<IBinder>& token, const std::vector<sp<WindowInfoHandle>>& windows,
+        sp<WindowInfoHandle>& outFocusableWindow) {
     bool allWindowsAreFocusable = true;
-    bool visibleWindowFound = false;
     bool windowFound = false;
+    sp<WindowInfoHandle> visibleWindowHandle = nullptr;
     for (const sp<WindowInfoHandle>& window : windows) {
         if (window->getToken() != token) {
             continue;
@@ -146,7 +168,7 @@
         windowFound = true;
         if (!window->getInfo()->inputConfig.test(gui::WindowInfo::InputConfig::NOT_VISIBLE)) {
             // Check if at least a single window is visible.
-            visibleWindowFound = true;
+            visibleWindowHandle = window;
         }
         if (window->getInfo()->inputConfig.test(gui::WindowInfo::InputConfig::NOT_FOCUSABLE)) {
             // Check if all windows with the window token are focusable.
@@ -161,10 +183,12 @@
     if (!allWindowsAreFocusable) {
         return Focusability::NOT_FOCUSABLE;
     }
-    if (!visibleWindowFound) {
+    if (!visibleWindowHandle) {
         return Focusability::NOT_VISIBLE;
     }
 
+    // Only set the outFoundWindow if the window can be focused
+    outFocusableWindow = visibleWindowHandle;
     return Focusability::OK;
 }
 
diff --git a/services/inputflinger/dispatcher/FocusResolver.h b/services/inputflinger/dispatcher/FocusResolver.h
index 6d11a77..5bb157b 100644
--- a/services/inputflinger/dispatcher/FocusResolver.h
+++ b/services/inputflinger/dispatcher/FocusResolver.h
@@ -92,7 +92,13 @@
     //
     static Focusability isTokenFocusable(
             const sp<IBinder>& token,
-            const std::vector<sp<android::gui::WindowInfoHandle>>& windows);
+            const std::vector<sp<android::gui::WindowInfoHandle>>& windows,
+            sp<android::gui::WindowInfoHandle>& outFocusableWindow);
+
+    static FocusResolver::Focusability getResolvedFocusWindow(
+            const sp<IBinder>& token,
+            const std::vector<sp<android::gui::WindowInfoHandle>>& windows,
+            sp<android::gui::WindowInfoHandle>& outFocusableWindow);
 
     // Focus tracking for keys, trackball, etc. A window token can be associated with one or
     // more InputWindowHandles. If a window is mirrored, the window and its mirror will share
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 78cdd0d..a3c129e 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -31,6 +31,7 @@
 #endif
 #include <input/InputDevice.h>
 #include <input/PrintTools.h>
+#include <openssl/mem.h>
 #include <powermanager/PowerManager.h>
 #include <unistd.h>
 #include <utils/Trace.h>
@@ -53,6 +54,7 @@
 #define INDENT4 "        "
 
 using namespace android::ftl::flag_operators;
+using android::base::Error;
 using android::base::HwTimeoutMultiplier;
 using android::base::Result;
 using android::base::StringPrintf;
@@ -110,15 +112,13 @@
 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);
 }
 
-inline const char* toString(bool value) {
-    return value ? "true" : "false";
-}
-
-inline const std::string toString(const sp<IBinder>& binder) {
+inline const std::string binderToString(const sp<IBinder>& binder) {
     if (binder == nullptr) {
         return "<null>";
     }
@@ -130,48 +130,68 @@
             AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
 }
 
-bool isValidKeyAction(int32_t action) {
+Result<void> checkKeyAction(int32_t action) {
     switch (action) {
         case AKEY_EVENT_ACTION_DOWN:
         case AKEY_EVENT_ACTION_UP:
-            return true;
+            return {};
         default:
-            return false;
+            return Error() << "Key event has invalid action code " << action;
     }
 }
 
-bool validateKeyEvent(int32_t action) {
-    if (!isValidKeyAction(action)) {
-        ALOGE("Key event has invalid action code 0x%x", action);
-        return false;
-    }
-    return true;
+Result<void> validateKeyEvent(int32_t action) {
+    return checkKeyAction(action);
 }
 
-bool isValidMotionAction(int32_t action, int32_t actionButton, int32_t pointerCount) {
+Result<void> checkMotionAction(int32_t action, int32_t actionButton, int32_t pointerCount) {
     switch (MotionEvent::getActionMasked(action)) {
         case AMOTION_EVENT_ACTION_DOWN:
-        case AMOTION_EVENT_ACTION_UP:
-            return pointerCount == 1;
+        case AMOTION_EVENT_ACTION_UP: {
+            if (pointerCount != 1) {
+                return Error() << "invalid pointer count " << pointerCount;
+            }
+            return {};
+        }
         case AMOTION_EVENT_ACTION_MOVE:
         case AMOTION_EVENT_ACTION_HOVER_ENTER:
         case AMOTION_EVENT_ACTION_HOVER_MOVE:
-        case AMOTION_EVENT_ACTION_HOVER_EXIT:
-            return pointerCount >= 1;
+        case AMOTION_EVENT_ACTION_HOVER_EXIT: {
+            if (pointerCount < 1) {
+                return Error() << "invalid pointer count " << pointerCount;
+            }
+            return {};
+        }
         case AMOTION_EVENT_ACTION_CANCEL:
         case AMOTION_EVENT_ACTION_OUTSIDE:
         case AMOTION_EVENT_ACTION_SCROLL:
-            return true;
+            return {};
         case AMOTION_EVENT_ACTION_POINTER_DOWN:
         case AMOTION_EVENT_ACTION_POINTER_UP: {
             const int32_t index = MotionEvent::getActionIndex(action);
-            return index >= 0 && index < pointerCount && pointerCount > 1;
+            if (index < 0) {
+                return Error() << "invalid index " << index << " for "
+                               << MotionEvent::actionToString(action);
+            }
+            if (index >= pointerCount) {
+                return Error() << "invalid index " << index << " for pointerCount " << pointerCount;
+            }
+            if (pointerCount <= 1) {
+                return Error() << "invalid pointer count " << pointerCount << " for "
+                               << MotionEvent::actionToString(action);
+            }
+            return {};
         }
         case AMOTION_EVENT_ACTION_BUTTON_PRESS:
-        case AMOTION_EVENT_ACTION_BUTTON_RELEASE:
-            return actionButton != 0;
+        case AMOTION_EVENT_ACTION_BUTTON_RELEASE: {
+            if (actionButton == 0) {
+                return Error() << "action button should be nonzero for "
+                               << MotionEvent::actionToString(action);
+            }
+            return {};
+        }
         default:
-            return false;
+            return Error() << "invalid action " << action;
     }
 }
 
@@ -179,32 +199,50 @@
     return std::chrono::duration_cast<std::chrono::milliseconds>(t).count();
 }
 
-bool validateMotionEvent(int32_t action, int32_t actionButton, size_t pointerCount,
-                         const PointerProperties* pointerProperties) {
-    if (!isValidMotionAction(action, actionButton, pointerCount)) {
-        ALOGE("Motion event has invalid action code 0x%x", action);
-        return false;
+Result<void> validateMotionEvent(int32_t action, int32_t actionButton, size_t pointerCount,
+                                 const PointerProperties* pointerProperties) {
+    Result<void> actionCheck = checkMotionAction(action, actionButton, pointerCount);
+    if (!actionCheck.ok()) {
+        return actionCheck;
     }
     if (pointerCount < 1 || pointerCount > MAX_POINTERS) {
-        ALOGE("Motion event has invalid pointer count %zu; value must be between 1 and %zu.",
-              pointerCount, MAX_POINTERS);
-        return false;
+        return Error() << "Motion event has invalid pointer count " << pointerCount
+                       << "; value must be between 1 and " << MAX_POINTERS << ".";
     }
     std::bitset<MAX_POINTER_ID + 1> pointerIdBits;
     for (size_t i = 0; i < pointerCount; i++) {
         int32_t id = pointerProperties[i].id;
         if (id < 0 || id > MAX_POINTER_ID) {
-            ALOGE("Motion event has invalid pointer id %d; value must be between 0 and %d", id,
-                  MAX_POINTER_ID);
-            return false;
+            return Error() << "Motion event has invalid pointer id " << id
+                           << "; value must be between 0 and " << MAX_POINTER_ID;
         }
         if (pointerIdBits.test(id)) {
-            ALOGE("Motion event has duplicate pointer id %d", id);
-            return false;
+            return Error() << "Motion event has duplicate pointer id " << id;
         }
         pointerIdBits.set(id);
     }
-    return true;
+    return {};
+}
+
+Result<void> validateInputEvent(const InputEvent& event) {
+    switch (event.getType()) {
+        case InputEventType::KEY: {
+            const KeyEvent& key = static_cast<const KeyEvent&>(event);
+            const int32_t action = key.getAction();
+            return validateKeyEvent(action);
+        }
+        case InputEventType::MOTION: {
+            const MotionEvent& motion = static_cast<const MotionEvent&>(event);
+            const int32_t action = motion.getAction();
+            const size_t pointerCount = motion.getPointerCount();
+            const PointerProperties* pointerProperties = motion.getPointerProperties();
+            const int32_t actionButton = motion.getActionButton();
+            return validateMotionEvent(action, actionButton, pointerCount, pointerProperties);
+        }
+        default: {
+            return {};
+        }
+    }
 }
 
 std::string dumpRegion(const Region& region) {
@@ -460,14 +498,14 @@
 // Returns true if the event type passed as argument represents a user activity.
 bool isUserActivityEvent(const EventEntry& eventEntry) {
     switch (eventEntry.type) {
+        case EventEntry::Type::CONFIGURATION_CHANGED:
+        case EventEntry::Type::DEVICE_RESET:
+        case EventEntry::Type::DRAG:
         case EventEntry::Type::FOCUS:
         case EventEntry::Type::POINTER_CAPTURE_CHANGED:
-        case EventEntry::Type::DRAG:
-        case EventEntry::Type::TOUCH_MODE_CHANGED:
         case EventEntry::Type::SENSOR:
-        case EventEntry::Type::CONFIGURATION_CHANGED:
+        case EventEntry::Type::TOUCH_MODE_CHANGED:
             return false;
-        case EventEntry::Type::DEVICE_RESET:
         case EventEntry::Type::KEY:
         case EventEntry::Type::MOTION:
             return true;
@@ -475,8 +513,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 +524,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 +588,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) {
@@ -616,7 +661,9 @@
             touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_HOVER_ENTER;
         } else {
             // This pointer was already sent to the window. Use ACTION_HOVER_MOVE.
-            LOG_ALWAYS_FATAL_IF(maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE);
+            if (CC_UNLIKELY(maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE)) {
+                LOG(FATAL) << "Expected ACTION_HOVER_MOVE instead of " << entry.getDescription();
+            }
             touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_IS;
         }
         touchedWindow.pointerIds.set(pointerId);
@@ -634,14 +681,33 @@
     return left;
 }
 
+// Filter windows in a TouchState and targets in a vector to remove untrusted windows/targets from
+// both.
+void filterUntrustedTargets(TouchState& touchState, std::vector<InputTarget>& targets) {
+    std::erase_if(touchState.windows, [&](const TouchedWindow& window) {
+        if (!window.windowHandle->getInfo()->inputConfig.test(
+                    WindowInfo::InputConfig::TRUSTED_OVERLAY)) {
+            // In addition to TouchState, erase this window from the input targets! We don't have a
+            // good way to do this today except by adding a nested loop.
+            // TODO(b/282025641): simplify this code once InputTargets are being identified
+            // separately from TouchedWindows.
+            std::erase_if(targets, [&](const InputTarget& target) {
+                return target.inputChannel->getConnectionToken() == window.windowHandle->getToken();
+            });
+            return true;
+        }
+        return false;
+    });
+}
+
 } // namespace
 
 // --- InputDispatcher ---
 
-InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy)
+InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy)
       : InputDispatcher(policy, STALE_EVENT_TIMEOUT) {}
 
-InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy,
+InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy,
                                  std::chrono::nanoseconds staleEventTimeout)
       : mPolicy(policy),
         mPendingEvent(nullptr),
@@ -668,7 +734,6 @@
     SurfaceComposerClient::getDefault()->addWindowInfosListener(mWindowInfoListener);
 #endif
     mKeyRepeatState.lastKeyEntry = nullptr;
-    policy->getDispatcherConfiguration(&mConfig);
 }
 
 InputDispatcher::~InputDispatcher() {
@@ -680,7 +745,7 @@
     mCommandQueue.clear();
 
     while (!mConnectionsByToken.empty()) {
-        sp<Connection> connection = mConnectionsByToken.begin()->second;
+        std::shared_ptr<Connection> connection = mConnectionsByToken.begin()->second;
         removeInputChannelLocked(connection->inputChannel->getConnectionToken(), /*notify=*/false);
     }
 }
@@ -794,7 +859,7 @@
     }
 
     // If we reached here, we have an unresponsive connection.
-    sp<Connection> connection = getConnectionLocked(mAnrTracker.firstToken());
+    std::shared_ptr<Connection> connection = getConnectionLocked(mAnrTracker.firstToken());
     if (connection == nullptr) {
         ALOGE("Could not find connection for entry %" PRId64, mAnrTracker.firstTimeout());
         return nextAnrCheck;
@@ -807,7 +872,7 @@
 }
 
 std::chrono::nanoseconds InputDispatcher::getDispatchingTimeoutLocked(
-        const sp<Connection>& connection) {
+        const std::shared_ptr<Connection>& connection) {
     if (connection->monitor) {
         return mMonitorDispatchingTimeout;
     }
@@ -1050,7 +1115,8 @@
         const std::vector<sp<WindowInfoHandle>> touchedSpies =
                 findTouchedSpyWindowsAtLocked(displayId, x, y, isStylus);
         for (const auto& windowHandle : touchedSpies) {
-            const sp<Connection> connection = getConnectionLocked(windowHandle->getToken());
+            const std::shared_ptr<Connection> connection =
+                    getConnectionLocked(windowHandle->getToken());
             if (connection != nullptr && connection->responsive) {
                 // This spy window could take more input. Drop all events preceding this
                 // event, so that the spy window can get a chance to receive the stream.
@@ -1159,7 +1225,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 +1236,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 +1251,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()) {
@@ -1288,7 +1355,7 @@
             (keyEntry.policyFlags & POLICY_FLAG_PASS_TO_USER);
 }
 
-bool InputDispatcher::isAppSwitchPendingLocked() {
+bool InputDispatcher::isAppSwitchPendingLocked() const {
     return mAppSwitchDueTime != LLONG_MAX;
 }
 
@@ -1392,7 +1459,7 @@
     // Enqueue a command to run outside the lock to tell the policy that the configuration changed.
     auto command = [this, eventTime = entry.eventTime]() REQUIRES(mLock) {
         scoped_unlock unlock(mLock);
-        mPolicy->notifyConfigurationChanged(eventTime);
+        mPolicy.notifyConfigurationChanged(eventTime);
     };
     postCommandLocked(std::move(command));
     return true;
@@ -1413,6 +1480,11 @@
     CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, "device was reset");
     options.deviceId = entry.deviceId;
     synthesizeCancelationEventsForAllConnectionsLocked(options);
+
+    // Remove all active pointers from this device
+    for (auto& [_, touchState] : mTouchStatesByDisplay) {
+        touchState.removeAllPointersForDevice(entry.deviceId);
+    }
     return true;
 }
 
@@ -1636,6 +1708,8 @@
                 doInterceptKeyBeforeDispatchingCommand(focusedWindowToken, *entry);
             };
             postCommandLocked(std::move(command));
+            // Poke user activity for keys not passed to user
+            pokeUserActivityLocked(*entry);
             return false; // wait for the command to run
         } else {
             entry->interceptKeyResult = KeyEntry::InterceptKeyResult::CONTINUE;
@@ -1652,6 +1726,8 @@
                            *dropReason == DropReason::POLICY ? InputEventInjectionResult::SUCCEEDED
                                                              : InputEventInjectionResult::FAILED);
         mReporter->reportDroppedKey(entry->id);
+        // Poke user activity for undispatched keys
+        pokeUserActivityLocked(*entry);
         return true;
     }
 
@@ -1707,10 +1783,10 @@
         scoped_unlock unlock(mLock);
 
         if (entry->accuracyChanged) {
-            mPolicy->notifySensorAccuracy(entry->deviceId, entry->sensorType, entry->accuracy);
+            mPolicy.notifySensorAccuracy(entry->deviceId, entry->sensorType, entry->accuracy);
         }
-        mPolicy->notifySensorEvent(entry->deviceId, entry->sensorType, entry->accuracy,
-                                   entry->hwTimestamp, entry->values);
+        mPolicy.notifySensorEvent(entry->deviceId, entry->sensorType, entry->accuracy,
+                                  entry->hwTimestamp, entry->values);
     };
     postCommandLocked(std::move(command));
 }
@@ -1852,11 +1928,12 @@
               entry.yPrecision, entry.downTime);
 
         for (uint32_t i = 0; i < entry.pointerCount; i++) {
-            ALOGD("  Pointer %d: id=%d, toolType=%d, "
+            ALOGD("  Pointer %d: id=%d, toolType=%s, "
                   "x=%f, y=%f, pressure=%f, size=%f, "
                   "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
                   "orientation=%f",
-                  i, entry.pointerProperties[i].id, entry.pointerProperties[i].toolType,
+                  i, entry.pointerProperties[i].id,
+                  ftl::enum_string(entry.pointerProperties[i].toolType).c_str(),
                   entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),
                   entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y),
                   entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
@@ -1885,7 +1962,7 @@
     pokeUserActivityLocked(*eventEntry);
 
     for (const InputTarget& inputTarget : inputTargets) {
-        sp<Connection> connection =
+        std::shared_ptr<Connection> connection =
                 getConnectionLocked(inputTarget.inputChannel->getConnectionToken());
         if (connection != nullptr) {
             prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget);
@@ -1899,7 +1976,7 @@
     }
 }
 
-void InputDispatcher::cancelEventsForAnrLocked(const sp<Connection>& connection) {
+void InputDispatcher::cancelEventsForAnrLocked(const std::shared_ptr<Connection>& connection) {
     // We will not be breaking any connections here, even if the policy wants us to abort dispatch.
     // If the policy decides to close the app, we will get a channel removal event via
     // unregisterInputChannel, and will clean up the connection that way. We are already not
@@ -2091,7 +2168,7 @@
     std::vector<Monitor> responsiveMonitors;
     std::copy_if(monitors.begin(), monitors.end(), std::back_inserter(responsiveMonitors),
                  [this](const Monitor& monitor) REQUIRES(mLock) {
-                     sp<Connection> connection =
+                     std::shared_ptr<Connection> connection =
                              getConnectionLocked(monitor.inputChannel->getConnectionToken());
                      if (connection == nullptr) {
                          ALOGE("Could not find connection for monitor %s",
@@ -2154,7 +2231,7 @@
     // event injection will be allowed.
     const int32_t displayId = entry.displayId;
     const int32_t action = entry.action;
-    const int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
+    const int32_t maskedAction = MotionEvent::getActionMasked(action);
 
     // Update the touch state as needed based on the properties of the touch event.
     outInjectionResult = InputEventInjectionResult::PENDING;
@@ -2181,7 +2258,9 @@
     const bool wasDown = oldState != nullptr && oldState->isDown();
     const bool isDown = (maskedAction == AMOTION_EVENT_ACTION_DOWN) ||
             (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN && !wasDown);
-    const bool newGesture = isDown || maskedAction == AMOTION_EVENT_ACTION_SCROLL || isHoverAction;
+    const bool newGesture = isDown || maskedAction == AMOTION_EVENT_ACTION_SCROLL ||
+            maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
+            maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE;
     const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE);
 
     // If pointers are already down, let's finish the current gesture and ignore the new events
@@ -2231,8 +2310,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 +2348,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 {};
@@ -2281,16 +2360,11 @@
                 continue;
             }
 
-            if (isHoverAction) {
+            if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
+                maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
                 const int32_t pointerId = entry.pointerProperties[0].id;
-                if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) {
-                    // Pointer left. Remove it
-                    tempTouchState.removeHoveringPointer(entry.deviceId, pointerId);
-                } else {
-                    // The "windowHandle" is the target of this hovering pointer.
-                    tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId,
-                                                              pointerId);
-                }
+                // The "windowHandle" is the target of this hovering pointer.
+                tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointerId);
             }
 
             // Set target flags.
@@ -2374,7 +2448,7 @@
         /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */
 
         // If the pointer is not currently down, then ignore the event.
-        if (!tempTouchState.isDown()) {
+        if (!tempTouchState.isDown() && maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) {
             LOG(INFO) << "Dropping event because the pointer is not down or we previously "
                          "dropped the pointer down event in display "
                       << displayId << ": " << entry.getDescription();
@@ -2382,6 +2456,20 @@
             return {};
         }
 
+        // If the pointer is not currently hovering, then ignore the event.
+        if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) {
+            const int32_t pointerId = entry.pointerProperties[0].id;
+            if (oldState == nullptr ||
+                oldState->getWindowsWithHoveringPointer(entry.deviceId, pointerId).empty()) {
+                LOG(INFO) << "Dropping event because the hovering pointer is not in any windows in "
+                             "display "
+                          << displayId << ": " << entry.getDescription();
+                outInjectionResult = InputEventInjectionResult::FAILED;
+                return {};
+            }
+            tempTouchState.removeHoveringPointer(entry.deviceId, pointerId);
+        }
+
         addDragEventLocked(entry);
 
         // Check whether touches should slip outside of the current foreground window.
@@ -2391,6 +2479,7 @@
             const bool isStylus = isPointerFromStylus(entry, /*pointerIndex=*/0);
             sp<WindowInfoHandle> oldTouchedWindowHandle =
                     tempTouchState.getFirstForegroundWindowHandle();
+            LOG_ALWAYS_FATAL_IF(oldTouchedWindowHandle == nullptr);
             auto [newTouchedWindowHandle, _] = findTouchedWindowAtLocked(displayId, x, y, isStylus);
 
             // Verify targeted injection.
@@ -2406,13 +2495,12 @@
                 newTouchedWindowHandle = nullptr;
             }
 
-            if (oldTouchedWindowHandle != newTouchedWindowHandle &&
-                oldTouchedWindowHandle != nullptr && newTouchedWindowHandle != nullptr) {
-                if (DEBUG_FOCUS) {
-                    ALOGD("Touch is slipping out of window %s into window %s in display %" PRId32,
-                          oldTouchedWindowHandle->getName().c_str(),
-                          newTouchedWindowHandle->getName().c_str(), displayId);
-                }
+            if (newTouchedWindowHandle != nullptr &&
+                !haveSameToken(oldTouchedWindowHandle, newTouchedWindowHandle)) {
+                ALOGD("Touch is slipping out of window %s into window %s in display %" PRId32,
+                      oldTouchedWindowHandle->getName().c_str(),
+                      newTouchedWindowHandle->getName().c_str(), displayId);
+
                 // Make a slippery exit from the old window.
                 std::bitset<MAX_POINTER_ID + 1> pointerIds;
                 const int32_t pointerId = entry.pointerProperties[0].id;
@@ -2478,21 +2566,6 @@
                                   targets);
         }
     }
-    // Ensure that we have at least one foreground window or at least one window that cannot be a
-    // foreground target. If we only have windows that are not receiving foreground touches (e.g. we
-    // only have windows getting ACTION_OUTSIDE), then drop the event, because there is no window
-    // that is actually receiving the entire gesture.
-    if (std::none_of(tempTouchState.windows.begin(), tempTouchState.windows.end(),
-                     [](const TouchedWindow& touchedWindow) {
-                         return !canReceiveForegroundTouches(
-                                        *touchedWindow.windowHandle->getInfo()) ||
-                                 touchedWindow.targetFlags.test(InputTarget::Flags::FOREGROUND);
-                     })) {
-        ALOGI("Dropping event because there is no touched window on display %d to receive it: %s",
-              displayId, entry.getDescription().c_str());
-        outInjectionResult = InputEventInjectionResult::FAILED;
-        return {};
-    }
 
     // Ensure that all touched windows are valid for injection.
     if (entry.injectionState != nullptr) {
@@ -2534,18 +2607,44 @@
         }
     }
 
-    // Success!  Output targets from the touch state.
+    // If this is a touchpad navigation gesture, it needs to only be sent to trusted targets, as we
+    // only want the system UI to handle these gestures.
+    const bool isTouchpadNavGesture = isFromSource(entry.source, AINPUT_SOURCE_MOUSE) &&
+            entry.classification == MotionClassification::MULTI_FINGER_SWIPE;
+    if (isTouchpadNavGesture) {
+        filterUntrustedTargets(/* byref */ tempTouchState, /* byref */ targets);
+    }
+
+    // Output targets from the touch state.
     for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
         if (touchedWindow.pointerIds.none() && !touchedWindow.hasHoveringPointers(entry.deviceId)) {
             // Windows with hovering pointers are getting persisted inside TouchState.
             // Do not send this event to those windows.
             continue;
         }
+
         addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
                               touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget,
                               targets);
     }
 
+    if (targets.empty()) {
+        LOG(INFO) << "Dropping event because no targets were found: " << entry.getDescription();
+        outInjectionResult = InputEventInjectionResult::FAILED;
+        return {};
+    }
+
+    // If we only have windows getting ACTION_OUTSIDE, then drop the event, because there is no
+    // window that is actually receiving the entire gesture.
+    if (std::all_of(targets.begin(), targets.end(), [](const InputTarget& target) {
+            return target.flags.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE);
+        })) {
+        LOG(INFO) << "Dropping event because all windows would just receive ACTION_OUTSIDE: "
+                  << entry.getDescription();
+        outInjectionResult = InputEventInjectionResult::FAILED;
+        return {};
+    }
+
     outInjectionResult = InputEventInjectionResult::SUCCEEDED;
     // Drop the outside or hover touch windows since we will not care about them
     // in the next iteration.
@@ -2656,9 +2755,6 @@
 
     if (uint32_t(pointerIndex) == entry.pointerCount) {
         LOG_ALWAYS_FATAL("Should find a valid pointer index by id %d", mDragState->pointerId);
-        sendDropWindowCommandLocked(nullptr, 0, 0);
-        mDragState.reset();
-        return;
     }
 
     const int32_t maskedAction = entry.action & AMOTION_EVENT_ACTION_MASK;
@@ -2895,7 +2991,7 @@
                         info->frameBottom, dumpRegion(info->touchableRegion).c_str(),
                         info->name.c_str(), info->inputConfig.string().c_str(),
                         toString(info->token != nullptr), info->applicationInfo.name.c_str(),
-                        toString(info->applicationInfo.token).c_str());
+                        binderToString(info->applicationInfo.token).c_str());
 }
 
 bool InputDispatcher::isTouchTrustedLocked(const TouchOcclusionInfo& occlusionInfo) const {
@@ -2970,13 +3066,11 @@
     }
     int32_t displayId = getTargetDisplayId(eventEntry);
     sp<WindowInfoHandle> focusedWindowHandle = getFocusedWindowHandleLocked(displayId);
+    const WindowInfo* windowDisablingUserActivityInfo = nullptr;
     if (focusedWindowHandle != nullptr) {
         const WindowInfo* info = focusedWindowHandle->getInfo();
         if (info->inputConfig.test(WindowInfo::InputConfig::DISABLE_USER_ACTIVITY)) {
-            if (DEBUG_DISPATCH_CYCLE) {
-                ALOGD("Not poking user activity: disabled by window '%s'.", info->name.c_str());
-            }
-            return;
+            windowDisablingUserActivityInfo = info;
         }
     }
 
@@ -2987,7 +3081,13 @@
             if (motionEntry.action == AMOTION_EVENT_ACTION_CANCEL) {
                 return;
             }
-
+            if (windowDisablingUserActivityInfo != nullptr) {
+                if (DEBUG_DISPATCH_CYCLE) {
+                    ALOGD("Not poking user activity: disabled by window '%s'.",
+                          windowDisablingUserActivityInfo->name.c_str());
+                }
+                return;
+            }
             if (MotionEvent::isTouchEvent(motionEntry.source, motionEntry.action)) {
                 eventType = USER_ACTIVITY_EVENT_TOUCH;
             }
@@ -2998,6 +3098,22 @@
             if (keyEntry.flags & AKEY_EVENT_FLAG_CANCELED) {
                 return;
             }
+            // If the key code is unknown, we don't consider it user activity
+            if (keyEntry.keyCode == AKEYCODE_UNKNOWN) {
+                return;
+            }
+            // Don't inhibit events that were intercepted or are not passed to
+            // the apps, like system shortcuts
+            if (windowDisablingUserActivityInfo != nullptr &&
+                keyEntry.interceptKeyResult != KeyEntry::InterceptKeyResult::SKIP &&
+                keyEntry.policyFlags & POLICY_FLAG_PASS_TO_USER) {
+                if (DEBUG_DISPATCH_CYCLE) {
+                    ALOGD("Not poking user activity: disabled by window '%s'.",
+                          windowDisablingUserActivityInfo->name.c_str());
+                }
+                return;
+            }
+
             eventType = USER_ACTIVITY_EVENT_BUTTON;
             break;
         }
@@ -3011,13 +3127,13 @@
     auto command = [this, eventTime = eventEntry.eventTime, eventType, displayId]()
                            REQUIRES(mLock) {
                                scoped_unlock unlock(mLock);
-                               mPolicy->pokeUserActivity(eventTime, eventType, displayId);
+                               mPolicy.pokeUserActivity(eventTime, eventType, displayId);
                            };
     postCommandLocked(std::move(command));
 }
 
 void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
-                                                 const sp<Connection>& connection,
+                                                 const std::shared_ptr<Connection>& connection,
                                                  std::shared_ptr<EventEntry> eventEntry,
                                                  const InputTarget& inputTarget) {
     if (ATRACE_ENABLED()) {
@@ -3087,7 +3203,7 @@
 }
 
 void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
-                                                   const sp<Connection>& connection,
+                                                   const std::shared_ptr<Connection>& connection,
                                                    std::shared_ptr<EventEntry> eventEntry,
                                                    const InputTarget& inputTarget) {
     if (ATRACE_ENABLED()) {
@@ -3121,7 +3237,7 @@
     }
 }
 
-void InputDispatcher::enqueueDispatchEntryLocked(const sp<Connection>& connection,
+void InputDispatcher::enqueueDispatchEntryLocked(const std::shared_ptr<Connection>& connection,
                                                  std::shared_ptr<EventEntry> eventEntry,
                                                  const InputTarget& inputTarget,
                                                  ftl::Flags<InputTarget::Flags> dispatchMode) {
@@ -3306,14 +3422,14 @@
     }
 
     std::unordered_set<sp<IBinder>, StrongPointerHash<IBinder>> newConnectionTokens;
-    std::vector<sp<Connection>> newConnections;
+    std::vector<std::shared_ptr<Connection>> newConnections;
     for (const InputTarget& target : targets) {
         if (target.flags.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE)) {
             continue; // Skip windows that receive ACTION_OUTSIDE
         }
 
         sp<IBinder> token = target.inputChannel->getConnectionToken();
-        sp<Connection> connection = getConnectionLocked(token);
+        std::shared_ptr<Connection> connection = getConnectionLocked(token);
         if (connection == nullptr) {
             continue;
         }
@@ -3326,7 +3442,7 @@
     mInteractionConnectionTokens = newConnectionTokens;
 
     std::string targetList;
-    for (const sp<Connection>& connection : newConnections) {
+    for (const std::shared_ptr<Connection>& connection : newConnections) {
         targetList += connection->getWindowName() + ", ";
     }
     std::string message = "Interaction with: " + targetList;
@@ -3352,7 +3468,7 @@
 
     auto command = [this, token]() REQUIRES(mLock) {
         scoped_unlock unlock(mLock);
-        mPolicy->onPointerDownOutsideFocus(token);
+        mPolicy.onPointerDownOutsideFocus(token);
     };
     postCommandLocked(std::move(command));
 }
@@ -3406,7 +3522,7 @@
 }
 
 void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
-                                               const sp<Connection>& connection) {
+                                               const std::shared_ptr<Connection>& connection) {
     if (ATRACE_ENABLED()) {
         std::string message = StringPrintf("startDispatchCycleLocked(inputChannel=%s)",
                                            connection->getInputChannelName().c_str());
@@ -3563,7 +3679,7 @@
 
 const std::array<uint8_t, 32> InputDispatcher::getSignature(
         const MotionEntry& motionEntry, const DispatchEntry& dispatchEntry) const {
-    const int32_t actionMasked = dispatchEntry.resolvedAction & AMOTION_EVENT_ACTION_MASK;
+    const int32_t actionMasked = MotionEvent::getActionMasked(dispatchEntry.resolvedAction);
     if (actionMasked != AMOTION_EVENT_ACTION_UP && actionMasked != AMOTION_EVENT_ACTION_DOWN) {
         // Only sign events up and down events as the purely move events
         // are tied to their up/down counterparts so signing would be redundant.
@@ -3586,8 +3702,8 @@
 }
 
 void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
-                                                const sp<Connection>& connection, uint32_t seq,
-                                                bool handled, nsecs_t consumeTime) {
+                                                const std::shared_ptr<Connection>& connection,
+                                                uint32_t seq, bool handled, nsecs_t consumeTime) {
     if (DEBUG_DISPATCH_CYCLE) {
         ALOGD("channel '%s' ~ finishDispatchCycle - seq=%u, handled=%s",
               connection->getInputChannelName().c_str(), seq, toString(handled));
@@ -3606,11 +3722,11 @@
 }
 
 void InputDispatcher::abortBrokenDispatchCycleLocked(nsecs_t currentTime,
-                                                     const sp<Connection>& connection,
+                                                     const std::shared_ptr<Connection>& connection,
                                                      bool notify) {
     if (DEBUG_DISPATCH_CYCLE) {
-        ALOGD("channel '%s' ~ abortBrokenDispatchCycle - notify=%s",
-              connection->getInputChannelName().c_str(), toString(notify));
+        LOG(DEBUG) << "channel '" << connection->getInputChannelName() << "'~ " << __func__
+                   << " - notify=" << toString(notify);
     }
 
     // Clear the dispatch queues.
@@ -3631,7 +3747,7 @@
 
             auto command = [this, connection]() REQUIRES(mLock) {
                 scoped_unlock unlock(mLock);
-                mPolicy->notifyInputChannelBroken(connection->inputChannel->getConnectionToken());
+                mPolicy.notifyInputChannelBroken(connection->inputChannel->getConnectionToken());
             };
             postCommandLocked(std::move(command));
         }
@@ -3655,7 +3771,7 @@
 
 int InputDispatcher::handleReceiveCallback(int events, sp<IBinder> connectionToken) {
     std::scoped_lock _l(mLock);
-    sp<Connection> connection = getConnectionLocked(connectionToken);
+    std::shared_ptr<Connection> connection = getConnectionLocked(connectionToken);
     if (connection == nullptr) {
         ALOGW("Received looper callback for unknown input channel token %p.  events=0x%x",
               connectionToken.get(), events);
@@ -3747,7 +3863,7 @@
 
 void InputDispatcher::synthesizeCancelationEventsForInputChannelLocked(
         const std::shared_ptr<InputChannel>& channel, const CancelationOptions& options) {
-    sp<Connection> connection = getConnectionLocked(channel->getConnectionToken());
+    std::shared_ptr<Connection> connection = getConnectionLocked(channel->getConnectionToken());
     if (connection == nullptr) {
         return;
     }
@@ -3756,7 +3872,7 @@
 }
 
 void InputDispatcher::synthesizeCancelationEventsForConnectionLocked(
-        const sp<Connection>& connection, const CancelationOptions& options) {
+        const std::shared_ptr<Connection>& connection, const CancelationOptions& options) {
     if (connection->status == Connection::Status::BROKEN) {
         return;
     }
@@ -3834,7 +3950,7 @@
 }
 
 void InputDispatcher::synthesizePointerDownEventsForConnectionLocked(
-        const nsecs_t downTime, const sp<Connection>& connection,
+        const nsecs_t downTime, const std::shared_ptr<Connection>& connection,
         ftl::Flags<InputTarget::Flags> targetFlags) {
     if (connection->status == Connection::Status::BROKEN) {
         return;
@@ -3899,7 +4015,8 @@
 void InputDispatcher::synthesizeCancelationEventsForWindowLocked(
         const sp<WindowInfoHandle>& windowHandle, const CancelationOptions& options) {
     if (windowHandle != nullptr) {
-        sp<Connection> wallpaperConnection = getConnectionLocked(windowHandle->getToken());
+        std::shared_ptr<Connection> wallpaperConnection =
+                getConnectionLocked(windowHandle->getToken());
         if (wallpaperConnection != nullptr) {
             synthesizeCancelationEventsForConnectionLocked(wallpaperConnection, options);
         }
@@ -4015,9 +4132,9 @@
     return splitMotionEntry;
 }
 
-void InputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) {
+void InputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
     if (debugInboundEventDetails()) {
-        ALOGD("notifyConfigurationChanged - eventTime=%" PRId64, args->eventTime);
+        ALOGD("notifyConfigurationChanged - eventTime=%" PRId64, args.eventTime);
     }
 
     bool needWake = false;
@@ -4025,7 +4142,7 @@
         std::scoped_lock _l(mLock);
 
         std::unique_ptr<ConfigurationChangedEntry> newEntry =
-                std::make_unique<ConfigurationChangedEntry>(args->id, args->eventTime);
+                std::make_unique<ConfigurationChangedEntry>(args.id, args.eventTime);
         needWake = enqueueInboundEventLocked(std::move(newEntry));
     } // release lock
 
@@ -4073,23 +4190,24 @@
     }
 }
 
-void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
+void InputDispatcher::notifyKey(const NotifyKeyArgs& args) {
     ALOGD_IF(debugInboundEventDetails(),
              "notifyKey - id=%" PRIx32 ", eventTime=%" PRId64
              ", deviceId=%d, source=%s, displayId=%" PRId32
              "policyFlags=0x%x, action=%s, flags=0x%x, keyCode=%s, scanCode=0x%x, metaState=0x%x, "
              "downTime=%" PRId64,
-             args->id, args->eventTime, args->deviceId,
-             inputEventSourceToString(args->source).c_str(), args->displayId, args->policyFlags,
-             KeyEvent::actionToString(args->action), args->flags, KeyEvent::getLabel(args->keyCode),
-             args->scanCode, args->metaState, args->downTime);
-    if (!validateKeyEvent(args->action)) {
+             args.id, args.eventTime, args.deviceId, inputEventSourceToString(args.source).c_str(),
+             args.displayId, args.policyFlags, KeyEvent::actionToString(args.action), args.flags,
+             KeyEvent::getLabel(args.keyCode), args.scanCode, args.metaState, args.downTime);
+    Result<void> keyCheck = validateKeyEvent(args.action);
+    if (!keyCheck.ok()) {
+        LOG(ERROR) << "invalid key event: " << keyCheck.error();
         return;
     }
 
-    uint32_t policyFlags = args->policyFlags;
-    int32_t flags = args->flags;
-    int32_t metaState = args->metaState;
+    uint32_t policyFlags = args.policyFlags;
+    int32_t flags = args.flags;
+    int32_t metaState = args.metaState;
     // InputDispatcher tracks and generates key repeats on behalf of
     // whatever notifies it, so repeatCount should always be set to 0
     constexpr int32_t repeatCount = 0;
@@ -4103,16 +4221,16 @@
 
     policyFlags |= POLICY_FLAG_TRUSTED;
 
-    int32_t keyCode = args->keyCode;
-    accelerateMetaShortcuts(args->deviceId, args->action, keyCode, metaState);
+    int32_t keyCode = args.keyCode;
+    accelerateMetaShortcuts(args.deviceId, args.action, keyCode, metaState);
 
     KeyEvent event;
-    event.initialize(args->id, args->deviceId, args->source, args->displayId, INVALID_HMAC,
-                     args->action, flags, keyCode, args->scanCode, metaState, repeatCount,
-                     args->downTime, args->eventTime);
+    event.initialize(args.id, args.deviceId, args.source, args.displayId, INVALID_HMAC, args.action,
+                     flags, keyCode, args.scanCode, metaState, repeatCount, args.downTime,
+                     args.eventTime);
 
     android::base::Timer t;
-    mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
+    mPolicy.interceptKeyBeforeQueueing(event, /*byref*/ policyFlags);
     if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
         ALOGW("Excessive delay in interceptKeyBeforeQueueing; took %s ms",
               std::to_string(t.duration().count()).c_str());
@@ -4126,7 +4244,7 @@
             mLock.unlock();
 
             policyFlags |= POLICY_FLAG_FILTERED;
-            if (!mPolicy->filterInputEvent(&event, policyFlags)) {
+            if (!mPolicy.filterInputEvent(event, policyFlags)) {
                 return; // event was consumed by the filter
             }
 
@@ -4134,10 +4252,9 @@
         }
 
         std::unique_ptr<KeyEntry> newEntry =
-                std::make_unique<KeyEntry>(args->id, args->eventTime, args->deviceId, args->source,
-                                           args->displayId, policyFlags, args->action, flags,
-                                           keyCode, args->scanCode, metaState, repeatCount,
-                                           args->downTime);
+                std::make_unique<KeyEntry>(args.id, args.eventTime, args.deviceId, args.source,
+                                           args.displayId, policyFlags, args.action, flags, keyCode,
+                                           args.scanCode, metaState, repeatCount, args.downTime);
 
         needWake = enqueueInboundEventLocked(std::move(newEntry));
         mLock.unlock();
@@ -4148,50 +4265,51 @@
     }
 }
 
-bool InputDispatcher::shouldSendKeyToInputFilterLocked(const NotifyKeyArgs* args) {
+bool InputDispatcher::shouldSendKeyToInputFilterLocked(const NotifyKeyArgs& args) {
     return mInputFilterEnabled;
 }
 
-void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
+void InputDispatcher::notifyMotion(const NotifyMotionArgs& args) {
     if (debugInboundEventDetails()) {
         ALOGD("notifyMotion - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=%s, "
               "displayId=%" PRId32 ", policyFlags=0x%x, "
               "action=%s, actionButton=0x%x, flags=0x%x, metaState=0x%x, buttonState=0x%x, "
               "edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, xCursorPosition=%f, "
               "yCursorPosition=%f, downTime=%" PRId64,
-              args->id, args->eventTime, args->deviceId,
-              inputEventSourceToString(args->source).c_str(), args->displayId, args->policyFlags,
-              MotionEvent::actionToString(args->action).c_str(), args->actionButton, args->flags,
-              args->metaState, args->buttonState, args->edgeFlags, args->xPrecision,
-              args->yPrecision, args->xCursorPosition, args->yCursorPosition, args->downTime);
-        for (uint32_t i = 0; i < args->pointerCount; i++) {
+              args.id, args.eventTime, args.deviceId, inputEventSourceToString(args.source).c_str(),
+              args.displayId, args.policyFlags, MotionEvent::actionToString(args.action).c_str(),
+              args.actionButton, args.flags, args.metaState, args.buttonState, args.edgeFlags,
+              args.xPrecision, args.yPrecision, args.xCursorPosition, args.yCursorPosition,
+              args.downTime);
+        for (uint32_t i = 0; i < args.pointerCount; i++) {
             ALOGD("  Pointer %d: id=%d, toolType=%s, x=%f, y=%f, pressure=%f, size=%f, "
                   "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, orientation=%f",
-                  i, args->pointerProperties[i].id,
-                  motionToolTypeToString(args->pointerProperties[i].toolType),
-                  args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),
-                  args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y),
-                  args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
-                  args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE),
-                  args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
-                  args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
-                  args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
-                  args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
-                  args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION));
+                  i, args.pointerProperties[i].id,
+                  ftl::enum_string(args.pointerProperties[i].toolType).c_str(),
+                  args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),
+                  args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y),
+                  args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
+                  args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE),
+                  args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
+                  args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
+                  args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
+                  args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
+                  args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION));
         }
     }
 
-    if (!validateMotionEvent(args->action, args->actionButton, args->pointerCount,
-                             args->pointerProperties)) {
-        LOG(ERROR) << "Invalid event: " << args->dump();
+    Result<void> motionCheck = validateMotionEvent(args.action, args.actionButton,
+                                                   args.pointerCount, args.pointerProperties);
+    if (!motionCheck.ok()) {
+        LOG(ERROR) << "Invalid event: " << args.dump() << "; reason: " << motionCheck.error();
         return;
     }
 
-    uint32_t policyFlags = args->policyFlags;
+    uint32_t policyFlags = args.policyFlags;
     policyFlags |= POLICY_FLAG_TRUSTED;
 
     android::base::Timer t;
-    mPolicy->interceptMotionBeforeQueueing(args->displayId, args->eventTime, /*byref*/ policyFlags);
+    mPolicy.interceptMotionBeforeQueueing(args.displayId, args.eventTime, policyFlags);
     if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
         ALOGW("Excessive delay in interceptMotionBeforeQueueing; took %s ms",
               std::to_string(t.duration().count()).c_str());
@@ -4203,10 +4321,10 @@
         if (!(policyFlags & POLICY_FLAG_PASS_TO_USER)) {
             // Set the flag anyway if we already have an ongoing gesture. That would allow us to
             // complete the processing of the current stroke.
-            const auto touchStateIt = mTouchStatesByDisplay.find(args->displayId);
+            const auto touchStateIt = mTouchStatesByDisplay.find(args.displayId);
             if (touchStateIt != mTouchStatesByDisplay.end()) {
                 const TouchState& touchState = touchStateIt->second;
-                if (touchState.deviceId == args->deviceId && touchState.isDown()) {
+                if (touchState.deviceId == args.deviceId && touchState.isDown()) {
                     policyFlags |= POLICY_FLAG_PASS_TO_USER;
                 }
             }
@@ -4214,23 +4332,23 @@
 
         if (shouldSendMotionToInputFilterLocked(args)) {
             ui::Transform displayTransform;
-            if (const auto it = mDisplayInfos.find(args->displayId); it != mDisplayInfos.end()) {
+            if (const auto it = mDisplayInfos.find(args.displayId); it != mDisplayInfos.end()) {
                 displayTransform = it->second.transform;
             }
 
             mLock.unlock();
 
             MotionEvent event;
-            event.initialize(args->id, args->deviceId, args->source, args->displayId, INVALID_HMAC,
-                             args->action, args->actionButton, args->flags, args->edgeFlags,
-                             args->metaState, args->buttonState, args->classification,
-                             displayTransform, args->xPrecision, args->yPrecision,
-                             args->xCursorPosition, args->yCursorPosition, displayTransform,
-                             args->downTime, args->eventTime, args->pointerCount,
-                             args->pointerProperties, args->pointerCoords);
+            event.initialize(args.id, args.deviceId, args.source, args.displayId, INVALID_HMAC,
+                             args.action, args.actionButton, args.flags, args.edgeFlags,
+                             args.metaState, args.buttonState, args.classification,
+                             displayTransform, args.xPrecision, args.yPrecision,
+                             args.xCursorPosition, args.yCursorPosition, displayTransform,
+                             args.downTime, args.eventTime, args.pointerCount,
+                             args.pointerProperties, args.pointerCoords);
 
             policyFlags |= POLICY_FLAG_FILTERED;
-            if (!mPolicy->filterInputEvent(&event, policyFlags)) {
+            if (!mPolicy.filterInputEvent(event, policyFlags)) {
                 return; // event was consumed by the filter
             }
 
@@ -4239,21 +4357,20 @@
 
         // Just enqueue a new motion event.
         std::unique_ptr<MotionEntry> newEntry =
-                std::make_unique<MotionEntry>(args->id, args->eventTime, args->deviceId,
-                                              args->source, args->displayId, policyFlags,
-                                              args->action, args->actionButton, args->flags,
-                                              args->metaState, args->buttonState,
-                                              args->classification, args->edgeFlags,
-                                              args->xPrecision, args->yPrecision,
-                                              args->xCursorPosition, args->yCursorPosition,
-                                              args->downTime, args->pointerCount,
-                                              args->pointerProperties, args->pointerCoords);
+                std::make_unique<MotionEntry>(args.id, args.eventTime, args.deviceId, args.source,
+                                              args.displayId, policyFlags, args.action,
+                                              args.actionButton, args.flags, args.metaState,
+                                              args.buttonState, args.classification, args.edgeFlags,
+                                              args.xPrecision, args.yPrecision,
+                                              args.xCursorPosition, args.yCursorPosition,
+                                              args.downTime, args.pointerCount,
+                                              args.pointerProperties, args.pointerCoords);
 
-        if (args->id != android::os::IInputConstants::INVALID_INPUT_EVENT_ID &&
-            IdGenerator::getSource(args->id) == IdGenerator::Source::INPUT_READER &&
+        if (args.id != android::os::IInputConstants::INVALID_INPUT_EVENT_ID &&
+            IdGenerator::getSource(args.id) == IdGenerator::Source::INPUT_READER &&
             !mInputFilterEnabled) {
-            const bool isDown = args->action == AMOTION_EVENT_ACTION_DOWN;
-            mLatencyTracker.trackListener(args->id, isDown, args->eventTime, args->readTime);
+            const bool isDown = args.action == AMOTION_EVENT_ACTION_DOWN;
+            mLatencyTracker.trackListener(args.id, isDown, args.eventTime, args.readTime);
         }
 
         needWake = enqueueInboundEventLocked(std::move(newEntry));
@@ -4265,12 +4382,12 @@
     }
 }
 
-void InputDispatcher::notifySensor(const NotifySensorArgs* args) {
+void InputDispatcher::notifySensor(const NotifySensorArgs& args) {
     if (debugInboundEventDetails()) {
         ALOGD("notifySensor - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=0x%x, "
               " sensorType=%s",
-              args->id, args->eventTime, args->deviceId, args->source,
-              ftl::enum_string(args->sensorType).c_str());
+              args.id, args.eventTime, args.deviceId, args.source,
+              ftl::enum_string(args.sensorType).c_str());
     }
 
     bool needWake = false;
@@ -4279,10 +4396,9 @@
 
         // Just enqueue a new sensor event.
         std::unique_ptr<SensorEntry> newEntry =
-                std::make_unique<SensorEntry>(args->id, args->eventTime, args->deviceId,
-                                              args->source, /* policyFlags=*/0, args->hwTimestamp,
-                                              args->sensorType, args->accuracy,
-                                              args->accuracyChanged, args->values);
+                std::make_unique<SensorEntry>(args.id, args.eventTime, args.deviceId, args.source,
+                                              /* policyFlags=*/0, args.hwTimestamp, args.sensorType,
+                                              args.accuracy, args.accuracyChanged, args.values);
 
         needWake = enqueueInboundEventLocked(std::move(newEntry));
         mLock.unlock();
@@ -4293,34 +4409,34 @@
     }
 }
 
-void InputDispatcher::notifyVibratorState(const NotifyVibratorStateArgs* args) {
+void InputDispatcher::notifyVibratorState(const NotifyVibratorStateArgs& args) {
     if (debugInboundEventDetails()) {
-        ALOGD("notifyVibratorState - eventTime=%" PRId64 ", device=%d,  isOn=%d", args->eventTime,
-              args->deviceId, args->isOn);
+        ALOGD("notifyVibratorState - eventTime=%" PRId64 ", device=%d,  isOn=%d", args.eventTime,
+              args.deviceId, args.isOn);
     }
-    mPolicy->notifyVibratorState(args->deviceId, args->isOn);
+    mPolicy.notifyVibratorState(args.deviceId, args.isOn);
 }
 
-bool InputDispatcher::shouldSendMotionToInputFilterLocked(const NotifyMotionArgs* args) {
+bool InputDispatcher::shouldSendMotionToInputFilterLocked(const NotifyMotionArgs& args) {
     return mInputFilterEnabled;
 }
 
-void InputDispatcher::notifySwitch(const NotifySwitchArgs* args) {
+void InputDispatcher::notifySwitch(const NotifySwitchArgs& args) {
     if (debugInboundEventDetails()) {
         ALOGD("notifySwitch - eventTime=%" PRId64 ", policyFlags=0x%x, switchValues=0x%08x, "
               "switchMask=0x%08x",
-              args->eventTime, args->policyFlags, args->switchValues, args->switchMask);
+              args.eventTime, args.policyFlags, args.switchValues, args.switchMask);
     }
 
-    uint32_t policyFlags = args->policyFlags;
+    uint32_t policyFlags = args.policyFlags;
     policyFlags |= POLICY_FLAG_TRUSTED;
-    mPolicy->notifySwitch(args->eventTime, args->switchValues, args->switchMask, policyFlags);
+    mPolicy.notifySwitch(args.eventTime, args.switchValues, args.switchMask, policyFlags);
 }
 
-void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
+void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
     if (debugInboundEventDetails()) {
-        ALOGD("notifyDeviceReset - eventTime=%" PRId64 ", deviceId=%d", args->eventTime,
-              args->deviceId);
+        ALOGD("notifyDeviceReset - eventTime=%" PRId64 ", deviceId=%d", args.eventTime,
+              args.deviceId);
     }
 
     bool needWake = false;
@@ -4328,7 +4444,7 @@
         std::scoped_lock _l(mLock);
 
         std::unique_ptr<DeviceResetEntry> newEntry =
-                std::make_unique<DeviceResetEntry>(args->id, args->eventTime, args->deviceId);
+                std::make_unique<DeviceResetEntry>(args.id, args.eventTime, args.deviceId);
         needWake = enqueueInboundEventLocked(std::move(newEntry));
     } // release lock
 
@@ -4337,17 +4453,17 @@
     }
 }
 
-void InputDispatcher::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) {
+void InputDispatcher::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) {
     if (debugInboundEventDetails()) {
-        ALOGD("notifyPointerCaptureChanged - eventTime=%" PRId64 ", enabled=%s", args->eventTime,
-              args->request.enable ? "true" : "false");
+        ALOGD("notifyPointerCaptureChanged - eventTime=%" PRId64 ", enabled=%s", args.eventTime,
+              args.request.enable ? "true" : "false");
     }
 
     bool needWake = false;
     { // acquire lock
         std::scoped_lock _l(mLock);
-        auto entry = std::make_unique<PointerCaptureChangedEntry>(args->id, args->eventTime,
-                                                                  args->request);
+        auto entry =
+                std::make_unique<PointerCaptureChangedEntry>(args.id, args.eventTime, args.request);
         needWake = enqueueInboundEventLocked(std::move(entry));
     } // release lock
 
@@ -4361,11 +4477,17 @@
                                                             InputEventInjectionSync syncMode,
                                                             std::chrono::milliseconds timeout,
                                                             uint32_t policyFlags) {
+    Result<void> eventValidation = validateInputEvent(*event);
+    if (!eventValidation.ok()) {
+        LOG(INFO) << "Injection failed: invalid event: " << eventValidation.error();
+        return InputEventInjectionResult::FAILED;
+    }
+
     if (debugInboundEventDetails()) {
-        ALOGD("injectInputEvent - eventType=%d, targetUid=%s, syncMode=%d, timeout=%lld, "
-              "policyFlags=0x%08x",
-              event->getType(), targetUid ? std::to_string(*targetUid).c_str() : "none", syncMode,
-              timeout.count(), policyFlags);
+        LOG(DEBUG) << __func__ << ": targetUid=" << toString(targetUid)
+                   << ", syncMode=" << ftl::enum_string(syncMode) << ", timeout=" << timeout.count()
+                   << "ms, policyFlags=0x" << std::hex << policyFlags << std::dec
+                   << ", event=" << *event;
     }
     nsecs_t endTime = now() + std::chrono::duration_cast<std::chrono::nanoseconds>(timeout).count();
 
@@ -4384,13 +4506,9 @@
 
     std::queue<std::unique_ptr<EventEntry>> injectedEntries;
     switch (event->getType()) {
-        case AINPUT_EVENT_TYPE_KEY: {
+        case InputEventType::KEY: {
             const KeyEvent& incomingKey = static_cast<const KeyEvent&>(*event);
-            int32_t action = incomingKey.getAction();
-            if (!validateKeyEvent(action)) {
-                return InputEventInjectionResult::FAILED;
-            }
-
+            const int32_t action = incomingKey.getAction();
             int32_t flags = incomingKey.getFlags();
             if (policyFlags & POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY) {
                 flags |= AKEY_EVENT_FLAG_IS_ACCESSIBILITY_EVENT;
@@ -4411,7 +4529,7 @@
 
             if (!(policyFlags & POLICY_FLAG_FILTERED)) {
                 android::base::Timer t;
-                mPolicy->interceptKeyBeforeQueueing(&keyEvent, /*byref*/ policyFlags);
+                mPolicy.interceptKeyBeforeQueueing(keyEvent, /*byref*/ policyFlags);
                 if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
                     ALOGW("Excessive delay in interceptKeyBeforeQueueing; took %s ms",
                           std::to_string(t.duration().count()).c_str());
@@ -4430,27 +4548,20 @@
             break;
         }
 
-        case AINPUT_EVENT_TYPE_MOTION: {
+        case InputEventType::MOTION: {
             const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event);
-            const int32_t action = motionEvent.getAction();
             const bool isPointerEvent =
                     isFromSource(event->getSource(), AINPUT_SOURCE_CLASS_POINTER);
             // If a pointer event has no displayId specified, inject it to the default display.
             const uint32_t displayId = isPointerEvent && (event->getDisplayId() == ADISPLAY_ID_NONE)
                     ? ADISPLAY_ID_DEFAULT
                     : event->getDisplayId();
-            const size_t pointerCount = motionEvent.getPointerCount();
-            const PointerProperties* pointerProperties = motionEvent.getPointerProperties();
-            const int32_t actionButton = motionEvent.getActionButton();
             int32_t flags = motionEvent.getFlags();
-            if (!validateMotionEvent(action, actionButton, pointerCount, pointerProperties)) {
-                return InputEventInjectionResult::FAILED;
-            }
 
             if (!(policyFlags & POLICY_FLAG_FILTERED)) {
                 nsecs_t eventTime = motionEvent.getEventTime();
                 android::base::Timer t;
-                mPolicy->interceptMotionBeforeQueueing(displayId, eventTime, /*byref*/ policyFlags);
+                mPolicy.interceptMotionBeforeQueueing(displayId, eventTime, /*byref*/ policyFlags);
                 if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
                     ALOGW("Excessive delay in interceptMotionBeforeQueueing; took %s ms",
                           std::to_string(t.duration().count()).c_str());
@@ -4467,8 +4578,9 @@
             std::unique_ptr<MotionEntry> injectedEntry =
                     std::make_unique<MotionEntry>(motionEvent.getId(), *sampleEventTimes,
                                                   resolvedDeviceId, motionEvent.getSource(),
-                                                  displayId, policyFlags, action, actionButton,
-                                                  flags, motionEvent.getMetaState(),
+                                                  displayId, policyFlags, motionEvent.getAction(),
+                                                  motionEvent.getActionButton(), flags,
+                                                  motionEvent.getMetaState(),
                                                   motionEvent.getButtonState(),
                                                   motionEvent.getClassification(),
                                                   motionEvent.getEdgeFlags(),
@@ -4476,18 +4588,22 @@
                                                   motionEvent.getYPrecision(),
                                                   motionEvent.getRawXCursorPosition(),
                                                   motionEvent.getRawYCursorPosition(),
-                                                  motionEvent.getDownTime(), uint32_t(pointerCount),
-                                                  pointerProperties, samplePointerCoords);
+                                                  motionEvent.getDownTime(),
+                                                  motionEvent.getPointerCount(),
+                                                  motionEvent.getPointerProperties(),
+                                                  samplePointerCoords);
             transformMotionEntryForInjectionLocked(*injectedEntry, motionEvent.getTransform());
             injectedEntries.push(std::move(injectedEntry));
             for (size_t i = motionEvent.getHistorySize(); i > 0; i--) {
                 sampleEventTimes += 1;
-                samplePointerCoords += pointerCount;
+                samplePointerCoords += motionEvent.getPointerCount();
                 std::unique_ptr<MotionEntry> nextInjectedEntry =
                         std::make_unique<MotionEntry>(motionEvent.getId(), *sampleEventTimes,
                                                       resolvedDeviceId, motionEvent.getSource(),
-                                                      displayId, policyFlags, action, actionButton,
-                                                      flags, motionEvent.getMetaState(),
+                                                      displayId, policyFlags,
+                                                      motionEvent.getAction(),
+                                                      motionEvent.getActionButton(), flags,
+                                                      motionEvent.getMetaState(),
                                                       motionEvent.getButtonState(),
                                                       motionEvent.getClassification(),
                                                       motionEvent.getEdgeFlags(),
@@ -4496,7 +4612,8 @@
                                                       motionEvent.getRawXCursorPosition(),
                                                       motionEvent.getRawYCursorPosition(),
                                                       motionEvent.getDownTime(),
-                                                      uint32_t(pointerCount), pointerProperties,
+                                                      motionEvent.getPointerCount(),
+                                                      motionEvent.getPointerProperties(),
                                                       samplePointerCoords);
                 transformMotionEntryForInjectionLocked(*nextInjectedEntry,
                                                        motionEvent.getTransform());
@@ -4506,7 +4623,7 @@
         }
 
         default:
-            ALOGW("Cannot inject %s events", inputEventTypeToString(event->getType()));
+            LOG(WARNING) << "Cannot inject " << ftl::enum_string(event->getType()) << " events";
             return InputEventInjectionResult::FAILED;
     }
 
@@ -4596,14 +4713,14 @@
     std::array<uint8_t, 32> calculatedHmac;
     std::unique_ptr<VerifiedInputEvent> result;
     switch (event.getType()) {
-        case AINPUT_EVENT_TYPE_KEY: {
+        case InputEventType::KEY: {
             const KeyEvent& keyEvent = static_cast<const KeyEvent&>(event);
             VerifiedKeyEvent verifiedKeyEvent = verifiedKeyEventFromKeyEvent(keyEvent);
             result = std::make_unique<VerifiedKeyEvent>(verifiedKeyEvent);
             calculatedHmac = sign(verifiedKeyEvent);
             break;
         }
-        case AINPUT_EVENT_TYPE_MOTION: {
+        case InputEventType::MOTION: {
             const MotionEvent& motionEvent = static_cast<const MotionEvent&>(event);
             VerifiedMotionEvent verifiedMotionEvent =
                     verifiedMotionEventFromMotionEvent(motionEvent);
@@ -4619,7 +4736,7 @@
     if (calculatedHmac == INVALID_HMAC) {
         return nullptr;
     }
-    if (calculatedHmac != event.getHmac()) {
+    if (0 != CRYPTO_memcmp(calculatedHmac.data(), event.getHmac().data(), calculatedHmac.size())) {
         return nullptr;
     }
     return result;
@@ -4764,6 +4881,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();
@@ -4784,7 +4907,7 @@
         return false;
     }
 
-    sp<Connection> connection = getConnectionLocked(window->getToken());
+    std::shared_ptr<Connection> connection = getConnectionLocked(window->getToken());
     if (connection == nullptr) {
         ALOGW("Not sending touch to %s because there's no corresponding connection",
               window->getName().c_str());
@@ -4801,7 +4924,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());
             }
@@ -5306,8 +5429,8 @@
         }
 
         // Synthesize cancel for old window and down for new window.
-        sp<Connection> fromConnection = getConnectionLocked(fromToken);
-        sp<Connection> toConnection = getConnectionLocked(toToken);
+        std::shared_ptr<Connection> fromConnection = getConnectionLocked(fromToken);
+        std::shared_ptr<Connection> toConnection = getConnectionLocked(toToken);
         if (fromConnection != nullptr && toConnection != nullptr) {
             fromConnection->inputState.mergePointerStateTo(toConnection->inputState);
             CancelationOptions
@@ -5399,7 +5522,7 @@
     mReplacedKeys.clear();
 }
 
-void InputDispatcher::logDispatchStateLocked() {
+void InputDispatcher::logDispatchStateLocked() const {
     std::string dump;
     dumpDispatchStateLocked(dump);
 
@@ -5407,11 +5530,11 @@
     std::string line;
 
     while (std::getline(stream, line, '\n')) {
-        ALOGD("%s", line.c_str());
+        ALOGI("%s", line.c_str());
     }
 }
 
-std::string InputDispatcher::dumpPointerCaptureStateLocked() {
+std::string InputDispatcher::dumpPointerCaptureStateLocked() const {
     std::string dump;
 
     dump += StringPrintf(INDENT "Pointer Capture Requested: %s\n",
@@ -5429,7 +5552,7 @@
     return dump;
 }
 
-void InputDispatcher::dumpDispatchStateLocked(std::string& dump) {
+void InputDispatcher::dumpDispatchStateLocked(std::string& dump) const {
     dump += StringPrintf(INDENT "DispatchEnabled: %s\n", toString(mDispatchEnabled));
     dump += StringPrintf(INDENT "DispatchFrozen: %s\n", toString(mDispatchFrozen));
     dump += StringPrintf(INDENT "InputFilterEnabled: %s\n", toString(mInputFilterEnabled));
@@ -5499,14 +5622,14 @@
                                          windowInfo->frameTop, windowInfo->frameRight,
                                          windowInfo->frameBottom, windowInfo->globalScaleFactor,
                                          windowInfo->applicationInfo.name.c_str(),
-                                         toString(windowInfo->applicationInfo.token).c_str());
+                                         binderToString(windowInfo->applicationInfo.token).c_str());
                     dump += dumpRegion(windowInfo->touchableRegion);
                     dump += StringPrintf(", ownerPid=%d, ownerUid=%d, dispatchingTimeout=%" PRId64
                                          "ms, hasToken=%s, "
                                          "touchOcclusionMode=%s\n",
                                          windowInfo->ownerPid, windowInfo->ownerUid,
                                          millis(windowInfo->dispatchingTimeout),
-                                         toString(windowInfo->token != nullptr),
+                                         binderToString(windowInfo->token).c_str(),
                                          toString(windowInfo->touchOcclusionMode).c_str());
                     windowInfo->transform.dump(dump, "transform", INDENT4);
                 }
@@ -5517,6 +5640,14 @@
     } else {
         dump += INDENT "Displays: <none>\n";
     }
+    dump += INDENT "Window Infos:\n";
+    dump += StringPrintf(INDENT2 "vsync id: %" PRId64 "\n", mWindowInfosVsyncId);
+    dump += StringPrintf(INDENT2 "timestamp (ns): %" PRId64 "\n", mWindowInfosTimestamp);
+    dump += "\n";
+    dump += StringPrintf(INDENT2 "max update delay (ns): %" PRId64 "\n", mMaxWindowInfosDelay);
+    dump += StringPrintf(INDENT2 "max update delay vsync id: %" PRId64 "\n",
+                         mMaxWindowInfosDelayVsyncId);
+    dump += "\n";
 
     if (!mGlobalMonitorsByDisplay.empty()) {
         for (const auto& [displayId, monitors] : mGlobalMonitorsByDisplay) {
@@ -5532,7 +5663,7 @@
     // Dump recently dispatched or dropped events from oldest to newest.
     if (!mRecentQueue.empty()) {
         dump += StringPrintf(INDENT "RecentQueue: length=%zu\n", mRecentQueue.size());
-        for (std::shared_ptr<EventEntry>& entry : mRecentQueue) {
+        for (const std::shared_ptr<EventEntry>& entry : mRecentQueue) {
             dump += INDENT2;
             dump += entry->getDescription();
             dump += StringPrintf(", age=%" PRId64 "ms\n", ns2ms(currentTime - entry->eventTime));
@@ -5555,7 +5686,7 @@
     // Dump inbound events from oldest to newest.
     if (!mInboundQueue.empty()) {
         dump += StringPrintf(INDENT "InboundQueue: length=%zu\n", mInboundQueue.size());
-        for (std::shared_ptr<EventEntry>& entry : mInboundQueue) {
+        for (const std::shared_ptr<EventEntry>& entry : mInboundQueue) {
             dump += INDENT2;
             dump += entry->getDescription();
             dump += StringPrintf(", age=%" PRId64 "ms\n", ns2ms(currentTime - entry->eventTime));
@@ -5637,7 +5768,7 @@
     dump += mLatencyAggregator.dump(INDENT2);
 }
 
-void InputDispatcher::dumpMonitors(std::string& dump, const std::vector<Monitor>& monitors) {
+void InputDispatcher::dumpMonitors(std::string& dump, const std::vector<Monitor>& monitors) const {
     const size_t numMonitors = monitors.size();
     for (size_t i = 0; i < numMonitors; i++) {
         const Monitor& monitor = monitors[i];
@@ -5673,8 +5804,9 @@
         std::scoped_lock _l(mLock);
         const sp<IBinder>& token = serverChannel->getConnectionToken();
         int fd = serverChannel->getFd();
-        sp<Connection> connection =
-                sp<Connection>::make(std::move(serverChannel), /*monitor=*/false, mIdGenerator);
+        std::shared_ptr<Connection> connection =
+                std::make_shared<Connection>(std::move(serverChannel), /*monitor=*/false,
+                                             mIdGenerator);
 
         if (mConnectionsByToken.find(token) != mConnectionsByToken.end()) {
             ALOGE("Created a new connection, but the token %p is already known", token.get());
@@ -5711,8 +5843,8 @@
                                           << " without a specified display.";
         }
 
-        sp<Connection> connection =
-                sp<Connection>::make(serverChannel, /*monitor=*/true, mIdGenerator);
+        std::shared_ptr<Connection> connection =
+                std::make_shared<Connection>(serverChannel, /*monitor=*/true, mIdGenerator);
         const sp<IBinder>& token = serverChannel->getConnectionToken();
         const int fd = serverChannel->getFd();
 
@@ -5752,7 +5884,7 @@
 
 status_t InputDispatcher::removeInputChannelLocked(const sp<IBinder>& connectionToken,
                                                    bool notify) {
-    sp<Connection> connection = getConnectionLocked(connectionToken);
+    std::shared_ptr<Connection> connection = getConnectionLocked(connectionToken);
     if (connection == nullptr) {
         // Connection can be removed via socket hang up or an explicit call to 'removeInputChannel'
         return BAD_VALUE;
@@ -5898,7 +6030,8 @@
     return std::nullopt;
 }
 
-sp<Connection> InputDispatcher::getConnectionLocked(const sp<IBinder>& inputConnectionToken) const {
+std::shared_ptr<Connection> InputDispatcher::getConnectionLocked(
+        const sp<IBinder>& inputConnectionToken) const {
     if (inputConnectionToken == nullptr) {
         return nullptr;
     }
@@ -5913,21 +6046,22 @@
 }
 
 std::string InputDispatcher::getConnectionNameLocked(const sp<IBinder>& connectionToken) const {
-    sp<Connection> connection = getConnectionLocked(connectionToken);
+    std::shared_ptr<Connection> connection = getConnectionLocked(connectionToken);
     if (connection == nullptr) {
         return "<nullptr>";
     }
     return connection->getInputChannelName();
 }
 
-void InputDispatcher::removeConnectionLocked(const sp<Connection>& connection) {
+void InputDispatcher::removeConnectionLocked(const std::shared_ptr<Connection>& connection) {
     mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken());
     mConnectionsByToken.erase(connection->inputChannel->getConnectionToken());
 }
 
 void InputDispatcher::doDispatchCycleFinishedCommand(nsecs_t finishTime,
-                                                     const sp<Connection>& connection, uint32_t seq,
-                                                     bool handled, nsecs_t consumeTime) {
+                                                     const std::shared_ptr<Connection>& connection,
+                                                     uint32_t seq, bool handled,
+                                                     nsecs_t consumeTime) {
     // Handle post-event policy actions.
     std::deque<DispatchEntry*>::iterator dispatchEntryIt = connection->findWaitQueueEntry(seq);
     if (dispatchEntryIt == connection->waitQueue.end()) {
@@ -5992,7 +6126,7 @@
                                                     const sp<IBinder>& newToken) {
     auto command = [this, oldToken, newToken]() REQUIRES(mLock) {
         scoped_unlock unlock(mLock);
-        mPolicy->notifyFocusChanged(oldToken, newToken);
+        mPolicy.notifyFocusChanged(oldToken, newToken);
     };
     postCommandLocked(std::move(command));
 }
@@ -6000,12 +6134,12 @@
 void InputDispatcher::sendDropWindowCommandLocked(const sp<IBinder>& token, float x, float y) {
     auto command = [this, token, x, y]() REQUIRES(mLock) {
         scoped_unlock unlock(mLock);
-        mPolicy->notifyDropWindow(token, x, y);
+        mPolicy.notifyDropWindow(token, x, y);
     };
     postCommandLocked(std::move(command));
 }
 
-void InputDispatcher::onAnrLocked(const sp<Connection>& connection) {
+void InputDispatcher::onAnrLocked(const std::shared_ptr<Connection>& connection) {
     if (connection == nullptr) {
         LOG_ALWAYS_FATAL("Caller must check for nullness");
     }
@@ -6047,7 +6181,7 @@
 
     auto command = [this, application = std::move(application)]() REQUIRES(mLock) {
         scoped_unlock unlock(mLock);
-        mPolicy->notifyNoFocusedWindowAnr(application);
+        mPolicy.notifyNoFocusedWindowAnr(application);
     };
     postCommandLocked(std::move(command));
 }
@@ -6087,8 +6221,7 @@
     { // release lock
         scoped_unlock unlock(mLock);
         android::base::Timer t;
-        delay = mPolicy->interceptKeyBeforeDispatching(focusedWindowToken, &event,
-                                                       entry.policyFlags);
+        delay = mPolicy.interceptKeyBeforeDispatching(focusedWindowToken, event, entry.policyFlags);
         if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
             ALOGW("Excessive delay in interceptKeyBeforeDispatching; took %s ms",
                   std::to_string(t.duration().count()).c_str());
@@ -6110,7 +6243,7 @@
                                                           std::string reason) {
     auto command = [this, token, pid, reason = std::move(reason)]() REQUIRES(mLock) {
         scoped_unlock unlock(mLock);
-        mPolicy->notifyWindowUnresponsive(token, pid, reason);
+        mPolicy.notifyWindowUnresponsive(token, pid, reason);
     };
     postCommandLocked(std::move(command));
 }
@@ -6119,7 +6252,7 @@
                                                         std::optional<int32_t> pid) {
     auto command = [this, token, pid]() REQUIRES(mLock) {
         scoped_unlock unlock(mLock);
-        mPolicy->notifyWindowResponsive(token, pid);
+        mPolicy.notifyWindowResponsive(token, pid);
     };
     postCommandLocked(std::move(command));
 }
@@ -6167,9 +6300,9 @@
     sendWindowResponsiveCommandLocked(connectionToken, pid);
 }
 
-bool InputDispatcher::afterKeyEventLockedInterruptable(const sp<Connection>& connection,
-                                                       DispatchEntry* dispatchEntry,
-                                                       KeyEntry& keyEntry, bool handled) {
+bool InputDispatcher::afterKeyEventLockedInterruptable(
+        const std::shared_ptr<Connection>& connection, DispatchEntry* dispatchEntry,
+        KeyEntry& keyEntry, bool handled) {
     if (keyEntry.flags & AKEY_EVENT_FLAG_FALLBACK) {
         if (!handled) {
             // Report the key as unhandled, since the fallback was not handled.
@@ -6181,7 +6314,7 @@
     // Get the fallback key state.
     // Clear it out after dispatching the UP.
     int32_t originalKeyCode = keyEntry.keyCode;
-    int32_t fallbackKeyCode = connection->inputState.getFallbackKey(originalKeyCode);
+    std::optional<int32_t> fallbackKeyCode = connection->inputState.getFallbackKey(originalKeyCode);
     if (keyEntry.action == AKEY_EVENT_ACTION_UP) {
         connection->inputState.removeFallbackKey(originalKeyCode);
     }
@@ -6190,7 +6323,7 @@
         // If the application handles the original key for which we previously
         // generated a fallback or if the window is not a foreground window,
         // then cancel the associated fallback key, if any.
-        if (fallbackKeyCode != -1) {
+        if (fallbackKeyCode) {
             // Dispatch the unhandled key to the policy with the cancel flag.
             if (DEBUG_OUTBOUND_EVENT_DETAILS) {
                 ALOGD("Unhandled key event: Asking policy to cancel fallback action.  "
@@ -6203,18 +6336,22 @@
 
             mLock.unlock();
 
-            mPolicy->dispatchUnhandledKey(connection->inputChannel->getConnectionToken(), &event,
-                                          keyEntry.policyFlags, &event);
+            if (const auto unhandledKeyFallback =
+                        mPolicy.dispatchUnhandledKey(connection->inputChannel->getConnectionToken(),
+                                                     event, keyEntry.policyFlags);
+                unhandledKeyFallback) {
+                event = *unhandledKeyFallback;
+            }
 
             mLock.lock();
 
             // Cancel the fallback key.
-            if (fallbackKeyCode != AKEYCODE_UNKNOWN) {
+            if (*fallbackKeyCode != AKEYCODE_UNKNOWN) {
                 CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS,
                                            "application handled the original non-fallback key "
                                            "or is no longer a foreground target, "
                                            "canceling previously dispatched fallback key");
-                options.keyCode = fallbackKeyCode;
+                options.keyCode = *fallbackKeyCode;
                 synthesizeCancelationEventsForConnectionLocked(connection, options);
             }
             connection->inputState.removeFallbackKey(originalKeyCode);
@@ -6224,7 +6361,7 @@
         // that we are in a good state to perform unhandled key event processing
         // Then ask the policy what to do with it.
         bool initialDown = keyEntry.action == AKEY_EVENT_ACTION_DOWN && keyEntry.repeatCount == 0;
-        if (fallbackKeyCode == -1 && !initialDown) {
+        if (!fallbackKeyCode && !initialDown) {
             if (DEBUG_OUTBOUND_EVENT_DETAILS) {
                 ALOGD("Unhandled key event: Skipping unhandled key event processing "
                       "since this is not an initial down.  "
@@ -6244,9 +6381,13 @@
 
         mLock.unlock();
 
-        bool fallback =
-                mPolicy->dispatchUnhandledKey(connection->inputChannel->getConnectionToken(),
-                                              &event, keyEntry.policyFlags, &event);
+        bool fallback = false;
+        if (auto fb = mPolicy.dispatchUnhandledKey(connection->inputChannel->getConnectionToken(),
+                                                   event, keyEntry.policyFlags);
+            fb) {
+            fallback = true;
+            event = *fb;
+        }
 
         mLock.lock();
 
@@ -6259,53 +6400,53 @@
         // The fallback keycode cannot change at any other point in the lifecycle.
         if (initialDown) {
             if (fallback) {
-                fallbackKeyCode = event.getKeyCode();
+                *fallbackKeyCode = event.getKeyCode();
             } else {
-                fallbackKeyCode = AKEYCODE_UNKNOWN;
+                *fallbackKeyCode = AKEYCODE_UNKNOWN;
             }
-            connection->inputState.setFallbackKey(originalKeyCode, fallbackKeyCode);
+            connection->inputState.setFallbackKey(originalKeyCode, *fallbackKeyCode);
         }
 
-        ALOG_ASSERT(fallbackKeyCode != -1);
+        ALOG_ASSERT(fallbackKeyCode);
 
         // Cancel the fallback key if the policy decides not to send it anymore.
         // We will continue to dispatch the key to the policy but we will no
         // longer dispatch a fallback key to the application.
-        if (fallbackKeyCode != AKEYCODE_UNKNOWN &&
-            (!fallback || fallbackKeyCode != event.getKeyCode())) {
+        if (*fallbackKeyCode != AKEYCODE_UNKNOWN &&
+            (!fallback || *fallbackKeyCode != event.getKeyCode())) {
             if (DEBUG_OUTBOUND_EVENT_DETAILS) {
                 if (fallback) {
                     ALOGD("Unhandled key event: Policy requested to send key %d"
                           "as a fallback for %d, but on the DOWN it had requested "
                           "to send %d instead.  Fallback canceled.",
-                          event.getKeyCode(), originalKeyCode, fallbackKeyCode);
+                          event.getKeyCode(), originalKeyCode, *fallbackKeyCode);
                 } else {
                     ALOGD("Unhandled key event: Policy did not request fallback for %d, "
                           "but on the DOWN it had requested to send %d.  "
                           "Fallback canceled.",
-                          originalKeyCode, fallbackKeyCode);
+                          originalKeyCode, *fallbackKeyCode);
                 }
             }
 
             CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS,
                                        "canceling fallback, policy no longer desires it");
-            options.keyCode = fallbackKeyCode;
+            options.keyCode = *fallbackKeyCode;
             synthesizeCancelationEventsForConnectionLocked(connection, options);
 
             fallback = false;
-            fallbackKeyCode = AKEYCODE_UNKNOWN;
+            *fallbackKeyCode = AKEYCODE_UNKNOWN;
             if (keyEntry.action != AKEY_EVENT_ACTION_UP) {
-                connection->inputState.setFallbackKey(originalKeyCode, fallbackKeyCode);
+                connection->inputState.setFallbackKey(originalKeyCode, *fallbackKeyCode);
             }
         }
 
         if (DEBUG_OUTBOUND_EVENT_DETAILS) {
             {
                 std::string msg;
-                const KeyedVector<int32_t, int32_t>& fallbackKeys =
+                const std::map<int32_t, int32_t>& fallbackKeys =
                         connection->inputState.getFallbackKeys();
-                for (size_t i = 0; i < fallbackKeys.size(); i++) {
-                    msg += StringPrintf(", %d->%d", fallbackKeys.keyAt(i), fallbackKeys.valueAt(i));
+                for (const auto& [key, value] : fallbackKeys) {
+                    msg += StringPrintf(", %d->%d", key, value);
                 }
                 ALOGD("Unhandled key event: %zu currently tracked fallback keys%s.",
                       fallbackKeys.size(), msg.c_str());
@@ -6319,7 +6460,7 @@
             keyEntry.source = event.getSource();
             keyEntry.displayId = event.getDisplayId();
             keyEntry.flags = event.getFlags() | AKEY_EVENT_FLAG_FALLBACK;
-            keyEntry.keyCode = fallbackKeyCode;
+            keyEntry.keyCode = *fallbackKeyCode;
             keyEntry.scanCode = event.getScanCode();
             keyEntry.metaState = event.getMetaState();
             keyEntry.repeatCount = event.getRepeatCount();
@@ -6329,7 +6470,7 @@
             if (DEBUG_OUTBOUND_EVENT_DETAILS) {
                 ALOGD("Unhandled key event: Dispatching fallback key.  "
                       "originalKeyCode=%d, fallbackKeyCode=%d, fallbackMetaState=%08x",
-                      originalKeyCode, fallbackKeyCode, keyEntry.metaState);
+                      originalKeyCode, *fallbackKeyCode, keyEntry.metaState);
             }
             return true; // restart the event
         } else {
@@ -6344,9 +6485,9 @@
     return false;
 }
 
-bool InputDispatcher::afterMotionEventLockedInterruptable(const sp<Connection>& connection,
-                                                          DispatchEntry* dispatchEntry,
-                                                          MotionEntry& motionEntry, bool handled) {
+bool InputDispatcher::afterMotionEventLockedInterruptable(
+        const std::shared_ptr<Connection>& connection, DispatchEntry* dispatchEntry,
+        MotionEntry& motionEntry, bool handled) {
     return false;
 }
 
@@ -6498,7 +6639,7 @@
     mCurrentPointerCaptureRequest.seq++;
     auto command = [this, request = mCurrentPointerCaptureRequest]() REQUIRES(mLock) {
         scoped_unlock unlock(mLock);
-        mPolicy->setPointerCapture(request);
+        mPolicy.setPointerCapture(request);
     };
     postCommandLocked(std::move(command));
 }
@@ -6522,12 +6663,11 @@
     mLooper->wake();
 }
 
-void InputDispatcher::onWindowInfosChanged(const std::vector<WindowInfo>& windowInfos,
-                                           const std::vector<DisplayInfo>& displayInfos) {
+void InputDispatcher::onWindowInfosChanged(const gui::WindowInfosUpdate& update) {
     // The listener sends the windows as a flattened array. Separate the windows by display for
     // more convenient parsing.
     std::unordered_map<int32_t, std::vector<sp<WindowInfoHandle>>> handlesPerDisplay;
-    for (const auto& info : windowInfos) {
+    for (const auto& info : update.windowInfos) {
         handlesPerDisplay.emplace(info.displayId, std::vector<sp<WindowInfoHandle>>());
         handlesPerDisplay[info.displayId].push_back(sp<WindowInfoHandle>::make(info));
     }
@@ -6542,13 +6682,22 @@
         }
 
         mDisplayInfos.clear();
-        for (const auto& displayInfo : displayInfos) {
+        for (const auto& displayInfo : update.displayInfos) {
             mDisplayInfos.emplace(displayInfo.displayId, displayInfo);
         }
 
         for (const auto& [displayId, handles] : handlesPerDisplay) {
             setInputWindowsLocked(handles, displayId);
         }
+
+        mWindowInfosVsyncId = update.vsyncId;
+        mWindowInfosTimestamp = update.timestamp;
+
+        int64_t delay = systemTime() - update.timestamp;
+        if (delay > mMaxWindowInfosDelay) {
+            mMaxWindowInfosDelay = delay;
+            mMaxWindowInfosDelayVsyncId = update.vsyncId;
+        }
     }
     // Wake up poll loop since it may need to make new input dispatching choices.
     mLooper->wake();
@@ -6571,9 +6720,8 @@
 }
 
 void InputDispatcher::DispatcherWindowListener::onWindowInfosChanged(
-        const std::vector<gui::WindowInfo>& windowInfos,
-        const std::vector<DisplayInfo>& displayInfos) {
-    mDispatcher.onWindowInfosChanged(windowInfos, displayInfos);
+        const gui::WindowInfosUpdate& update) {
+    mDispatcher.onWindowInfosChanged(update);
 }
 
 void InputDispatcher::cancelCurrentTouch() {
@@ -6590,6 +6738,13 @@
     mLooper->wake();
 }
 
+void InputDispatcher::requestRefreshConfiguration() {
+    InputDispatcherConfiguration config = mPolicy.getDispatcherConfiguration();
+
+    std::scoped_lock _l(mLock);
+    mConfig = config;
+}
+
 void InputDispatcher::setMonitorDispatchingTimeoutForTest(std::chrono::nanoseconds timeout) {
     std::scoped_lock _l(mLock);
     mMonitorDispatchingTimeout = timeout;
@@ -6599,7 +6754,7 @@
                                          const sp<WindowInfoHandle>& oldWindowHandle,
                                          const sp<WindowInfoHandle>& newWindowHandle,
                                          TouchState& state, int32_t pointerId,
-                                         std::vector<InputTarget>& targets) {
+                                         std::vector<InputTarget>& targets) const {
     std::bitset<MAX_POINTER_ID + 1> pointerIds;
     pointerIds.set(pointerId);
     const bool oldHasWallpaper = oldWindowHandle->getInfo()->inputConfig.test(
@@ -6668,9 +6823,11 @@
         wallpaperFlags |= InputTarget::Flags::WINDOW_IS_OBSCURED |
                 InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED;
         state.addOrUpdateWindow(newWallpaper, wallpaperFlags, pointerIds, downTimeInTarget);
-        sp<Connection> wallpaperConnection = getConnectionLocked(newWallpaper->getToken());
+        std::shared_ptr<Connection> wallpaperConnection =
+                getConnectionLocked(newWallpaper->getToken());
         if (wallpaperConnection != nullptr) {
-            sp<Connection> toConnection = getConnectionLocked(toWindowHandle->getToken());
+            std::shared_ptr<Connection> toConnection =
+                    getConnectionLocked(toWindowHandle->getToken());
             toConnection->inputState.mergePointerStateTo(wallpaperConnection->inputState);
             synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, wallpaperConnection,
                                                            wallpaperFlags);
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index b94858b..9b12f2f 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -36,7 +36,7 @@
 
 #include <attestation/HmacKeyManager.h>
 #include <gui/InputApplication.h>
-#include <gui/WindowInfo.h>
+#include <gui/WindowInfosUpdate.h>
 #include <input/Input.h>
 #include <input/InputTransport.h>
 #include <limits.h>
@@ -82,8 +82,8 @@
 public:
     static constexpr bool kDefaultInTouchMode = true;
 
-    explicit InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy);
-    explicit InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy,
+    explicit InputDispatcher(InputDispatcherPolicyInterface& policy);
+    explicit InputDispatcher(InputDispatcherPolicyInterface& policy,
                              std::chrono::nanoseconds staleEventTimeout);
     ~InputDispatcher() override;
 
@@ -93,14 +93,15 @@
     status_t start() override;
     status_t stop() override;
 
-    void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override;
-    void notifyKey(const NotifyKeyArgs* args) override;
-    void notifyMotion(const NotifyMotionArgs* args) override;
-    void notifySwitch(const NotifySwitchArgs* args) override;
-    void notifySensor(const NotifySensorArgs* args) override;
-    void notifyVibratorState(const NotifyVibratorStateArgs* args) override;
-    void notifyDeviceReset(const NotifyDeviceResetArgs* args) override;
-    void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override;
+    void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override{};
+    void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
+    void notifyKey(const NotifyKeyArgs& args) override;
+    void notifyMotion(const NotifyMotionArgs& args) override;
+    void notifySwitch(const NotifySwitchArgs& args) override;
+    void notifySensor(const NotifySensorArgs& args) override;
+    void notifyVibratorState(const NotifyVibratorStateArgs& args) override;
+    void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
+    void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
 
     android::os::InputEventInjectionResult injectInputEvent(
             const InputEvent* event, std::optional<int32_t> targetUid,
@@ -143,11 +144,12 @@
     void displayRemoved(int32_t displayId) override;
 
     // Public because it's also used by tests to simulate the WindowInfosListener callback
-    void onWindowInfosChanged(const std::vector<android::gui::WindowInfo>& windowInfos,
-                              const std::vector<android::gui::DisplayInfo>& displayInfos);
+    void onWindowInfosChanged(const gui::WindowInfosUpdate&);
 
     void cancelCurrentTouch() override;
 
+    void requestRefreshConfiguration() override;
+
     // Public to allow tests to verify that a Monitor can get ANR.
     void setMonitorDispatchingTimeoutForTest(std::chrono::nanoseconds timeout);
 
@@ -164,8 +166,8 @@
 
     std::unique_ptr<InputThread> mThread;
 
-    sp<InputDispatcherPolicyInterface> mPolicy;
-    android::InputDispatcherConfiguration mConfig;
+    InputDispatcherPolicyInterface& mPolicy;
+    android::InputDispatcherConfiguration mConfig GUARDED_BY(mLock);
 
     std::mutex mLock;
 
@@ -202,6 +204,11 @@
 
     const IdGenerator mIdGenerator;
 
+    int64_t mWindowInfosVsyncId GUARDED_BY(mLock);
+    int64_t mWindowInfosTimestamp GUARDED_BY(mLock);
+    int64_t mMaxWindowInfosDelay GUARDED_BY(mLock) = -1;
+    int64_t mMaxWindowInfosDelayVsyncId GUARDED_BY(mLock) = -1;
+
     // With each iteration, InputDispatcher nominally processes one queued event,
     // a timeout, or a response from an input consumer.
     // This method should only be called on the input dispatcher's own thread.
@@ -231,7 +238,7 @@
     nsecs_t mAppSwitchDueTime GUARDED_BY(mLock);
 
     bool isAppSwitchKeyEvent(const KeyEntry& keyEntry);
-    bool isAppSwitchPendingLocked() REQUIRES(mLock);
+    bool isAppSwitchPendingLocked() const REQUIRES(mLock);
     void resetPendingAppSwitchLocked(bool handled) REQUIRES(mLock);
 
     // Blocked event latency optimization.  Drops old events when the user intends
@@ -239,21 +246,21 @@
     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);
 
-    sp<Connection> getConnectionLocked(const sp<IBinder>& inputConnectionToken) const
+    std::shared_ptr<Connection> getConnectionLocked(const sp<IBinder>& inputConnectionToken) const
             REQUIRES(mLock);
 
     std::string getConnectionNameLocked(const sp<IBinder>& connectionToken) const REQUIRES(mLock);
 
-    void removeConnectionLocked(const sp<Connection>& connection) REQUIRES(mLock);
+    void removeConnectionLocked(const std::shared_ptr<Connection>& connection) REQUIRES(mLock);
 
     status_t pilferPointersLocked(const sp<IBinder>& token) REQUIRES(mLock);
 
@@ -263,8 +270,8 @@
     };
 
     // All registered connections mapped by input channel token.
-    std::unordered_map<sp<IBinder>, sp<Connection>, StrongPointerHash<IBinder>> mConnectionsByToken
-            GUARDED_BY(mLock);
+    std::unordered_map<sp<IBinder>, std::shared_ptr<Connection>, StrongPointerHash<IBinder>>
+            mConnectionsByToken GUARDED_BY(mLock);
 
     // Find a monitor pid by the provided token.
     std::optional<int32_t> findMonitorPidByTokenLocked(const sp<IBinder>& token) REQUIRES(mLock);
@@ -327,12 +334,12 @@
     std::chrono::nanoseconds mMonitorDispatchingTimeout GUARDED_BY(mLock);
 
     nsecs_t processAnrsLocked() REQUIRES(mLock);
-    std::chrono::nanoseconds getDispatchingTimeoutLocked(const sp<Connection>& connection)
-            REQUIRES(mLock);
+    std::chrono::nanoseconds getDispatchingTimeoutLocked(
+            const std::shared_ptr<Connection>& connection) REQUIRES(mLock);
 
     // Input filter processing.
-    bool shouldSendKeyToInputFilterLocked(const NotifyKeyArgs* args) REQUIRES(mLock);
-    bool shouldSendMotionToInputFilterLocked(const NotifyMotionArgs* args) REQUIRES(mLock);
+    bool shouldSendKeyToInputFilterLocked(const NotifyKeyArgs& args) REQUIRES(mLock);
+    bool shouldSendMotionToInputFilterLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
 
     // Inbound event processing.
     void drainInboundQueueLocked() REQUIRES(mLock);
@@ -353,9 +360,7 @@
     class DispatcherWindowListener : public gui::WindowInfosListener {
     public:
         explicit DispatcherWindowListener(InputDispatcher& dispatcher) : mDispatcher(dispatcher){};
-        void onWindowInfosChanged(
-                const std::vector<android::gui::WindowInfo>& windowInfos,
-                const std::vector<android::gui::DisplayInfo>& displayInfos) override;
+        void onWindowInfosChanged(const gui::WindowInfosUpdate&) override;
 
     private:
         InputDispatcher& mDispatcher;
@@ -374,6 +379,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.
@@ -531,7 +537,7 @@
     // prevent unneeded wakeups.
     AnrTracker mAnrTracker GUARDED_BY(mLock);
 
-    void cancelEventsForAnrLocked(const sp<Connection>& connection) REQUIRES(mLock);
+    void cancelEventsForAnrLocked(const std::shared_ptr<Connection>& connection) REQUIRES(mLock);
     // If a focused application changes, we should stop counting down the "no focused window" time,
     // because we will have no way of knowing when the previous application actually added a window.
     // This also means that we will miss cases like pulling down notification shade when the
@@ -592,22 +598,26 @@
     // These methods are deliberately not Interruptible because doing all of the work
     // with the mutex held makes it easier to ensure that connection invariants are maintained.
     // If needed, the methods post commands to run later once the critical bits are done.
-    void prepareDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
+    void prepareDispatchCycleLocked(nsecs_t currentTime,
+                                    const std::shared_ptr<Connection>& connection,
                                     std::shared_ptr<EventEntry>, const InputTarget& inputTarget)
             REQUIRES(mLock);
-    void enqueueDispatchEntriesLocked(nsecs_t currentTime, const sp<Connection>& connection,
+    void enqueueDispatchEntriesLocked(nsecs_t currentTime,
+                                      const std::shared_ptr<Connection>& connection,
                                       std::shared_ptr<EventEntry>, const InputTarget& inputTarget)
             REQUIRES(mLock);
-    void enqueueDispatchEntryLocked(const sp<Connection>& connection, std::shared_ptr<EventEntry>,
-                                    const InputTarget& inputTarget,
+    void enqueueDispatchEntryLocked(const std::shared_ptr<Connection>& connection,
+                                    std::shared_ptr<EventEntry>, const InputTarget& inputTarget,
                                     ftl::Flags<InputTarget::Flags> dispatchMode) REQUIRES(mLock);
     status_t publishMotionEvent(Connection& connection, DispatchEntry& dispatchEntry) const;
-    void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection)
+    void startDispatchCycleLocked(nsecs_t currentTime,
+                                  const std::shared_ptr<Connection>& connection) REQUIRES(mLock);
+    void finishDispatchCycleLocked(nsecs_t currentTime,
+                                   const std::shared_ptr<Connection>& connection, uint32_t seq,
+                                   bool handled, nsecs_t consumeTime) REQUIRES(mLock);
+    void abortBrokenDispatchCycleLocked(nsecs_t currentTime,
+                                        const std::shared_ptr<Connection>& connection, bool notify)
             REQUIRES(mLock);
-    void finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
-                                   uint32_t seq, bool handled, nsecs_t consumeTime) REQUIRES(mLock);
-    void abortBrokenDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
-                                        bool notify) REQUIRES(mLock);
     void drainDispatchQueue(std::deque<DispatchEntry*>& queue);
     void releaseDispatchEntry(DispatchEntry* dispatchEntry);
     int handleReceiveCallback(int events, sp<IBinder> connectionToken);
@@ -622,14 +632,13 @@
     void synthesizeCancelationEventsForInputChannelLocked(
             const std::shared_ptr<InputChannel>& channel, const CancelationOptions& options)
             REQUIRES(mLock);
-    void synthesizeCancelationEventsForConnectionLocked(const sp<Connection>& connection,
-                                                        const CancelationOptions& options)
+    void synthesizeCancelationEventsForConnectionLocked(
+            const std::shared_ptr<Connection>& connection, const CancelationOptions& options)
             REQUIRES(mLock);
 
-    void synthesizePointerDownEventsForConnectionLocked(const nsecs_t downTime,
-                                                        const sp<Connection>& connection,
-                                                        ftl::Flags<InputTarget::Flags> targetFlags)
-            REQUIRES(mLock);
+    void synthesizePointerDownEventsForConnectionLocked(
+            const nsecs_t downTime, const std::shared_ptr<Connection>& connection,
+            ftl::Flags<InputTarget::Flags> targetFlags) REQUIRES(mLock);
 
     void synthesizeCancelationEventsForWindowLocked(
             const sp<android::gui::WindowInfoHandle>& windowHandle,
@@ -645,10 +654,10 @@
     void resetAndDropEverythingLocked(const char* reason) REQUIRES(mLock);
 
     // Dump state.
-    void dumpDispatchStateLocked(std::string& dump) REQUIRES(mLock);
-    void dumpMonitors(std::string& dump, const std::vector<Monitor>& monitors);
-    void logDispatchStateLocked() REQUIRES(mLock);
-    std::string dumpPointerCaptureStateLocked() REQUIRES(mLock);
+    void dumpDispatchStateLocked(std::string& dump) const REQUIRES(mLock);
+    void dumpMonitors(std::string& dump, const std::vector<Monitor>& monitors) const;
+    void logDispatchStateLocked() const REQUIRES(mLock);
+    std::string dumpPointerCaptureStateLocked() const REQUIRES(mLock);
 
     // Registration.
     void removeMonitorChannelLocked(const sp<IBinder>& connectionToken) REQUIRES(mLock);
@@ -656,16 +665,16 @@
             REQUIRES(mLock);
 
     // Interesting events that we might like to log or tell the framework about.
-    void doDispatchCycleFinishedCommand(nsecs_t finishTime, const sp<Connection>& connection,
-                                        uint32_t seq, bool handled, nsecs_t consumeTime)
-            REQUIRES(mLock);
+    void doDispatchCycleFinishedCommand(nsecs_t finishTime,
+                                        const std::shared_ptr<Connection>& connection, uint32_t seq,
+                                        bool handled, nsecs_t consumeTime) REQUIRES(mLock);
     void doInterceptKeyBeforeDispatchingCommand(const sp<IBinder>& focusedWindowToken,
                                                 KeyEntry& entry) REQUIRES(mLock);
     void onFocusChangedLocked(const FocusResolver::FocusChanges& changes) REQUIRES(mLock);
     void sendFocusChangedCommandLocked(const sp<IBinder>& oldToken, const sp<IBinder>& newToken)
             REQUIRES(mLock);
     void sendDropWindowCommandLocked(const sp<IBinder>& token, float x, float y) REQUIRES(mLock);
-    void onAnrLocked(const sp<Connection>& connection) REQUIRES(mLock);
+    void onAnrLocked(const std::shared_ptr<Connection>& connection) REQUIRES(mLock);
     void onAnrLocked(std::shared_ptr<InputApplicationHandle> application) REQUIRES(mLock);
     void updateLastAnrStateLocked(const sp<android::gui::WindowInfoHandle>& window,
                                   const std::string& reason) REQUIRES(mLock);
@@ -673,10 +682,10 @@
                                   const std::string& reason) REQUIRES(mLock);
     void updateLastAnrStateLocked(const std::string& windowLabel, const std::string& reason)
             REQUIRES(mLock);
-    bool afterKeyEventLockedInterruptable(const sp<Connection>& connection,
+    bool afterKeyEventLockedInterruptable(const std::shared_ptr<Connection>& connection,
                                           DispatchEntry* dispatchEntry, KeyEntry& keyEntry,
                                           bool handled) REQUIRES(mLock);
-    bool afterMotionEventLockedInterruptable(const sp<Connection>& connection,
+    bool afterMotionEventLockedInterruptable(const std::shared_ptr<Connection>& connection,
                                              DispatchEntry* dispatchEntry, MotionEntry& motionEntry,
                                              bool handled) REQUIRES(mLock);
 
@@ -700,8 +709,8 @@
     void slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFlags,
                             const sp<android::gui::WindowInfoHandle>& oldWindowHandle,
                             const sp<android::gui::WindowInfoHandle>& newWindowHandle,
-                            TouchState& state, int32_t pointerId, std::vector<InputTarget>& targets)
-            REQUIRES(mLock);
+                            TouchState& state, int32_t pointerId,
+                            std::vector<InputTarget>& targets) const REQUIRES(mLock);
     void transferWallpaperTouch(ftl::Flags<InputTarget::Flags> oldTargetFlags,
                                 ftl::Flags<InputTarget::Flags> newTargetFlags,
                                 const sp<android::gui::WindowInfoHandle> fromWindowHandle,
diff --git a/services/inputflinger/dispatcher/InputDispatcherFactory.cpp b/services/inputflinger/dispatcher/InputDispatcherFactory.cpp
index bca1600..3ef8419 100644
--- a/services/inputflinger/dispatcher/InputDispatcherFactory.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcherFactory.cpp
@@ -20,7 +20,7 @@
 namespace android {
 
 std::unique_ptr<InputDispatcherInterface> createInputDispatcher(
-        const sp<InputDispatcherPolicyInterface>& policy) {
+        InputDispatcherPolicyInterface& policy) {
     return std::make_unique<android::inputdispatcher::InputDispatcher>(policy);
 }
 
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index 94f3813..4652c2d 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -42,13 +42,8 @@
     switch (action) {
         case AKEY_EVENT_ACTION_UP: {
             if (entry.flags & AKEY_EVENT_FLAG_FALLBACK) {
-                for (size_t i = 0; i < mFallbackKeys.size();) {
-                    if (mFallbackKeys.valueAt(i) == entry.keyCode) {
-                        mFallbackKeys.removeItemsAt(i);
-                    } else {
-                        i += 1;
-                    }
-                }
+                std::erase_if(mFallbackKeys,
+                              [&entry](const auto& item) { return item.second == entry.keyCode; });
             }
             ssize_t index = findKeyMemento(entry);
             if (index >= 0) {
@@ -481,22 +476,20 @@
     }
 }
 
-int32_t InputState::getFallbackKey(int32_t originalKeyCode) {
-    ssize_t index = mFallbackKeys.indexOfKey(originalKeyCode);
-    return index >= 0 ? mFallbackKeys.valueAt(index) : -1;
+std::optional<int32_t> InputState::getFallbackKey(int32_t originalKeyCode) {
+    auto it = mFallbackKeys.find(originalKeyCode);
+    if (it == mFallbackKeys.end()) {
+        return {};
+    }
+    return it->second;
 }
 
 void InputState::setFallbackKey(int32_t originalKeyCode, int32_t fallbackKeyCode) {
-    ssize_t index = mFallbackKeys.indexOfKey(originalKeyCode);
-    if (index >= 0) {
-        mFallbackKeys.replaceValueAt(index, fallbackKeyCode);
-    } else {
-        mFallbackKeys.add(originalKeyCode, fallbackKeyCode);
-    }
+    mFallbackKeys.insert_or_assign(originalKeyCode, fallbackKeyCode);
 }
 
 void InputState::removeFallbackKey(int32_t originalKeyCode) {
-    mFallbackKeys.removeItem(originalKeyCode);
+    mFallbackKeys.erase(originalKeyCode);
 }
 
 bool InputState::shouldCancelKey(const KeyMemento& memento, const CancelationOptions& options) {
diff --git a/services/inputflinger/dispatcher/InputState.h b/services/inputflinger/dispatcher/InputState.h
index d788e47..af2a3cb 100644
--- a/services/inputflinger/dispatcher/InputState.h
+++ b/services/inputflinger/dispatcher/InputState.h
@@ -62,9 +62,9 @@
     void mergePointerStateTo(InputState& other);
 
     // Gets the fallback key associated with a keycode.
-    // Returns -1 if none.
+    // Returns std::nullopt if none.
     // Returns AKEYCODE_UNKNOWN if we are only dispatching the unhandled key to the policy.
-    int32_t getFallbackKey(int32_t originalKeyCode);
+    std::optional<int32_t> getFallbackKey(int32_t originalKeyCode);
 
     // Sets the fallback key for a particular keycode.
     void setFallbackKey(int32_t originalKeyCode, int32_t fallbackKeyCode);
@@ -72,7 +72,7 @@
     // Removes the fallback key for a particular keycode.
     void removeFallbackKey(int32_t originalKeyCode);
 
-    inline const KeyedVector<int32_t, int32_t>& getFallbackKeys() const { return mFallbackKeys; }
+    inline const std::map<int32_t, int32_t>& getFallbackKeys() const { return mFallbackKeys; }
 
 private:
     struct KeyMemento {
@@ -113,7 +113,7 @@
 
     std::vector<KeyMemento> mKeyMementos;
     std::vector<MotionMemento> mMotionMementos;
-    KeyedVector<int32_t, int32_t> mFallbackKeys;
+    std::map</*originalKeyCode*/int32_t, /*fallbackKeyCode*/int32_t> mFallbackKeys;
 
     ssize_t findKeyMemento(const KeyEntry& entry) const;
     ssize_t findMotionMemento(const MotionEntry& entry, bool hovering) const;
diff --git a/services/inputflinger/dispatcher/LatencyAggregator.cpp b/services/inputflinger/dispatcher/LatencyAggregator.cpp
index a5bfc25..96d78c3 100644
--- a/services/inputflinger/dispatcher/LatencyAggregator.cpp
+++ b/services/inputflinger/dispatcher/LatencyAggregator.cpp
@@ -256,7 +256,7 @@
     }
 }
 
-std::string LatencyAggregator::dump(const char* prefix) {
+std::string LatencyAggregator::dump(const char* prefix) const {
     std::string sketchDump = StringPrintf("%s  Sketches:\n", prefix);
     for (size_t i = 0; i < SketchIndex::SIZE; i++) {
         const int64_t numDown = mDownSketches[i]->num_values();
diff --git a/services/inputflinger/dispatcher/LatencyAggregator.h b/services/inputflinger/dispatcher/LatencyAggregator.h
index accfc29..60b6813 100644
--- a/services/inputflinger/dispatcher/LatencyAggregator.h
+++ b/services/inputflinger/dispatcher/LatencyAggregator.h
@@ -56,7 +56,7 @@
      */
     void processTimeline(const InputEventTimeline& timeline) override;
 
-    std::string dump(const char* prefix);
+    std::string dump(const char* prefix) const;
 
     ~LatencyAggregator();
 
diff --git a/services/inputflinger/dispatcher/LatencyTracker.cpp b/services/inputflinger/dispatcher/LatencyTracker.cpp
index 2dd7d3f..b7c36a8 100644
--- a/services/inputflinger/dispatcher/LatencyTracker.cpp
+++ b/services/inputflinger/dispatcher/LatencyTracker.cpp
@@ -165,7 +165,7 @@
     }
 }
 
-std::string LatencyTracker::dump(const char* prefix) {
+std::string LatencyTracker::dump(const char* prefix) const {
     return StringPrintf("%sLatencyTracker:\n", prefix) +
             StringPrintf("%s  mTimelines.size() = %zu\n", prefix, mTimelines.size()) +
             StringPrintf("%s  mEventTimes.size() = %zu\n", prefix, mEventTimes.size());
diff --git a/services/inputflinger/dispatcher/LatencyTracker.h b/services/inputflinger/dispatcher/LatencyTracker.h
index 64dfeef..4212da8 100644
--- a/services/inputflinger/dispatcher/LatencyTracker.h
+++ b/services/inputflinger/dispatcher/LatencyTracker.h
@@ -55,7 +55,7 @@
     void trackGraphicsLatency(int32_t inputEventId, const sp<IBinder>& connectionToken,
                               std::array<nsecs_t, GraphicsTimeline::SIZE> timeline);
 
-    std::string dump(const char* prefix);
+    std::string dump(const char* prefix) const;
 
 private:
     /**
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index 9c443f1..0a61d48 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -242,6 +242,18 @@
     clearWindowsWithoutPointers();
 }
 
+void TouchState::removeAllPointersForDevice(int32_t removedDeviceId) {
+    for (TouchedWindow& window : windows) {
+        window.removeAllHoveringPointersForDevice(removedDeviceId);
+    }
+    if (deviceId == removedDeviceId) {
+        for (TouchedWindow& window : windows) {
+            window.removeAllTouchingPointers();
+        }
+    }
+    clearWindowsWithoutPointers();
+}
+
 std::string TouchState::dump() const {
     std::string out;
     out += StringPrintf("deviceId=%d, source=%s\n", deviceId,
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index a20080f..15b840f 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -54,6 +54,8 @@
                                     int32_t deviceId, int32_t hoveringPointerId);
     void removeHoveringPointer(int32_t deviceId, int32_t hoveringPointerId);
     void clearHoveringPointers();
+
+    void removeAllPointersForDevice(int32_t removedDeviceId);
     void removeWindowByToken(const sp<IBinder>& token);
     void filterNonAsIsTouchWindows();
 
diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp
index 99c4769..d55d657 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.cpp
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -58,6 +58,10 @@
     }
 }
 
+void TouchedWindow::removeAllTouchingPointers() {
+    pointerIds.reset();
+}
+
 void TouchedWindow::removeHoveringPointer(int32_t deviceId, int32_t pointerId) {
     const auto it = mHoveringPointerIdsByDevice.find(deviceId);
     if (it == mHoveringPointerIdsByDevice.end()) {
@@ -70,6 +74,10 @@
     }
 }
 
+void TouchedWindow::removeAllHoveringPointersForDevice(int32_t deviceId) {
+    mHoveringPointerIdsByDevice.erase(deviceId);
+}
+
 std::string TouchedWindow::dump() const {
     std::string out;
     std::string hoveringPointers =
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index aa2e9dd..43e7169 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -44,6 +44,9 @@
     void addHoveringPointer(int32_t deviceId, int32_t pointerId);
     void removeHoveringPointer(int32_t deviceId, int32_t pointerId);
     void removeTouchingPointer(int32_t pointerId);
+
+    void removeAllTouchingPointers();
+    void removeAllHoveringPointersForDevice(int32_t deviceId);
     void clearHoveringPointers();
     std::string dump() const;
 
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherFactory.h b/services/inputflinger/dispatcher/include/InputDispatcherFactory.h
index 5247d8e..6b298c2 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherFactory.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherFactory.h
@@ -25,6 +25,6 @@
 
 // This factory method is used to encapsulate implementation details in internal header files.
 std::unique_ptr<InputDispatcherInterface> createInputDispatcher(
-        const sp<InputDispatcherPolicyInterface>& policy);
+        InputDispatcherPolicyInterface& policy);
 
 } // namespace android
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index 76dce63..c752ddd 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -225,6 +225,12 @@
      * Abort the current touch stream.
      */
     virtual void cancelCurrentTouch() = 0;
+
+    /**
+     * Request that the InputDispatcher's configuration, which can be obtained through the policy,
+     * be updated.
+     */
+    virtual void requestRefreshConfiguration() = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
index 7843923..5539915 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -35,12 +35,11 @@
  * The actual implementation is partially supported by callbacks into the DVM
  * via JNI.  This interface is also mocked in the unit tests.
  */
-class InputDispatcherPolicyInterface : public virtual RefBase {
-protected:
-    InputDispatcherPolicyInterface() {}
-    virtual ~InputDispatcherPolicyInterface() {}
-
+class InputDispatcherPolicyInterface {
 public:
+    InputDispatcherPolicyInterface() = default;
+    virtual ~InputDispatcherPolicyInterface() = default;
+
     /* Notifies the system that a configuration change has occurred. */
     virtual void notifyConfigurationChanged(nsecs_t when) = 0;
 
@@ -75,14 +74,14 @@
     virtual void notifyVibratorState(int32_t deviceId, bool isOn) = 0;
 
     /* Gets the input dispatcher configuration. */
-    virtual void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) = 0;
+    virtual InputDispatcherConfiguration getDispatcherConfiguration() = 0;
 
     /* Filters an input event.
      * Return true to dispatch the event unmodified, false to consume the event.
      * A filter can also transform and inject events later by passing POLICY_FLAG_FILTERED
      * to injectInputEvent.
      */
-    virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) = 0;
+    virtual bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) = 0;
 
     /* Intercepts a key event immediately before queueing it.
      * The policy can use this method as an opportunity to perform power management functions
@@ -91,7 +90,7 @@
      * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event
      * should be dispatched to applications.
      */
-    virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) = 0;
+    virtual void interceptKeyBeforeQueueing(const KeyEvent& keyEvent, uint32_t& policyFlags) = 0;
 
     /* Intercepts a touch, trackball or other motion event before queueing it.
      * The policy can use this method as an opportunity to perform power management functions
@@ -100,18 +99,19 @@
      * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event
      * should be dispatched to applications.
      */
-    virtual void interceptMotionBeforeQueueing(const int32_t displayId, nsecs_t when,
+    virtual void interceptMotionBeforeQueueing(int32_t displayId, nsecs_t when,
                                                uint32_t& policyFlags) = 0;
 
     /* Allows the policy a chance to intercept a key before dispatching. */
     virtual nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token,
-                                                  const KeyEvent* keyEvent,
+                                                  const KeyEvent& keyEvent,
                                                   uint32_t policyFlags) = 0;
 
     /* Allows the policy a chance to perform default processing for an unhandled key.
-     * Returns an alternate keycode to redispatch as a fallback, or 0 to give up. */
-    virtual bool dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent* keyEvent,
-                                      uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) = 0;
+     * Returns an alternate key event to redispatch as a fallback, if needed. */
+    virtual std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>& token,
+                                                         const KeyEvent& keyEvent,
+                                                         uint32_t policyFlags) = 0;
 
     /* Notifies the policy about switch events.
      */
diff --git a/services/inputflinger/host/Android.bp b/services/inputflinger/host/Android.bp
index 743587c..4d2839f 100644
--- a/services/inputflinger/host/Android.bp
+++ b/services/inputflinger/host/Android.bp
@@ -23,7 +23,7 @@
 
 cc_library_shared {
     name: "libinputflingerhost",
-
+    cpp_std: "c++20",
     srcs: [
         "InputFlinger.cpp",
         "InputDriver.cpp",
@@ -64,14 +64,17 @@
 
     srcs: ["main.cpp"],
 
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 
     shared_libs: [
         "libbase",
         "libbinder",
         "libinputflingerhost",
         "libutils",
-        "libinput"
+        "libinput",
     ],
     static_libs: [
         "libarect",
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/InputListener.h b/services/inputflinger/include/InputListener.h
index 1bb1968..4f78f03 100644
--- a/services/inputflinger/include/InputListener.h
+++ b/services/inputflinger/include/InputListener.h
@@ -37,14 +37,15 @@
     InputListenerInterface& operator=(const InputListenerInterface&) = delete;
     virtual ~InputListenerInterface() { }
 
-    virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) = 0;
-    virtual void notifyKey(const NotifyKeyArgs* args) = 0;
-    virtual void notifyMotion(const NotifyMotionArgs* args) = 0;
-    virtual void notifySwitch(const NotifySwitchArgs* args) = 0;
-    virtual void notifySensor(const NotifySensorArgs* args) = 0;
-    virtual void notifyVibratorState(const NotifyVibratorStateArgs* args) = 0;
-    virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) = 0;
-    virtual void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) = 0;
+    virtual void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) = 0;
+    virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) = 0;
+    virtual void notifyKey(const NotifyKeyArgs& args) = 0;
+    virtual void notifyMotion(const NotifyMotionArgs& args) = 0;
+    virtual void notifySwitch(const NotifySwitchArgs& args) = 0;
+    virtual void notifySensor(const NotifySensorArgs& args) = 0;
+    virtual void notifyVibratorState(const NotifyVibratorStateArgs& args) = 0;
+    virtual void notifyDeviceReset(const NotifyDeviceResetArgs& args) = 0;
+    virtual void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) = 0;
 
     void notify(const NotifyArgs& args);
 };
@@ -58,14 +59,15 @@
 public:
     explicit QueuedInputListener(InputListenerInterface& innerListener);
 
-    virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override;
-    virtual void notifyKey(const NotifyKeyArgs* args) override;
-    virtual void notifyMotion(const NotifyMotionArgs* args) override;
-    virtual void notifySwitch(const NotifySwitchArgs* args) override;
-    virtual void notifySensor(const NotifySensorArgs* args) override;
-    virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) override;
-    void notifyVibratorState(const NotifyVibratorStateArgs* args) override;
-    void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override;
+    virtual void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
+    virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
+    virtual void notifyKey(const NotifyKeyArgs& args) override;
+    virtual void notifyMotion(const NotifyMotionArgs& args) override;
+    virtual void notifySwitch(const NotifySwitchArgs& args) override;
+    virtual void notifySensor(const NotifySensorArgs& args) override;
+    virtual void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
+    void notifyVibratorState(const NotifyVibratorStateArgs& args) override;
+    void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
 
     void flush();
 
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 841c914..a93a2ea 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -42,115 +42,6 @@
 
 namespace android {
 
-// --- InputReaderInterface ---
-
-/* The interface for the InputReader shared library.
- *
- * Manages one or more threads that process raw input events and sends cooked event data to an
- * input listener.
- *
- * The implementation must guarantee thread safety for this interface. However, since the input
- * listener is NOT thread safe, all calls to the listener must happen from the same thread.
- */
-class InputReaderInterface {
-public:
-    InputReaderInterface() { }
-    virtual ~InputReaderInterface() { }
-
-    /* Dumps the state of the input reader.
-     *
-     * This method may be called on any thread (usually by the input manager). */
-    virtual void dump(std::string& dump) = 0;
-
-    /* Called by the heartbeat to ensures that the reader has not deadlocked. */
-    virtual void monitor() = 0;
-
-    /* Returns true if the input device is enabled. */
-    virtual bool isInputDeviceEnabled(int32_t deviceId) = 0;
-
-    /* Makes the reader start processing events from the kernel. */
-    virtual status_t start() = 0;
-
-    /* Makes the reader stop processing any more events. */
-    virtual status_t stop() = 0;
-
-    /* Gets information about all input devices.
-     *
-     * This method may be called on any thread (usually by the input manager).
-     */
-    virtual std::vector<InputDeviceInfo> getInputDevices() const = 0;
-
-    /* Query current input state. */
-    virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
-            int32_t scanCode) = 0;
-    virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
-            int32_t keyCode) = 0;
-    virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask,
-            int32_t sw) = 0;
-
-    virtual void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode,
-                                 int32_t toKeyCode) const = 0;
-
-    virtual int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const = 0;
-
-    /* Toggle Caps Lock */
-    virtual void toggleCapsLockState(int32_t deviceId) = 0;
-
-    /* Determine whether physical keys exist for the given framework-domain key codes. */
-    virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask,
-                         const std::vector<int32_t>& keyCodes, uint8_t* outFlags) = 0;
-
-    /* Requests that a reconfiguration of all input devices.
-     * The changes flag is a bitfield that indicates what has changed and whether
-     * the input devices must all be reopened. */
-    virtual void requestRefreshConfiguration(uint32_t changes) = 0;
-
-    /* Controls the vibrator of a particular input device. */
-    virtual void vibrate(int32_t deviceId, const VibrationSequence& sequence, ssize_t repeat,
-                         int32_t token) = 0;
-    virtual void cancelVibrate(int32_t deviceId, int32_t token) = 0;
-
-    virtual bool isVibrating(int32_t deviceId) = 0;
-
-    virtual std::vector<int32_t> getVibratorIds(int32_t deviceId) = 0;
-    /* Get battery level of a particular input device. */
-    virtual std::optional<int32_t> getBatteryCapacity(int32_t deviceId) = 0;
-    /* Get battery status of a particular input device. */
-    virtual std::optional<int32_t> getBatteryStatus(int32_t deviceId) = 0;
-    /* Get the device path for the battery of an input device. */
-    virtual std::optional<std::string> getBatteryDevicePath(int32_t deviceId) = 0;
-
-    virtual std::vector<InputDeviceLightInfo> getLights(int32_t deviceId) = 0;
-
-    virtual std::vector<InputDeviceSensorInfo> getSensors(int32_t deviceId) = 0;
-
-    /* Return true if the device can send input events to the specified display. */
-    virtual bool canDispatchToDisplay(int32_t deviceId, int32_t displayId) = 0;
-
-    /* Enable sensor in input reader mapper. */
-    virtual bool enableSensor(int32_t deviceId, InputDeviceSensorType sensorType,
-                              std::chrono::microseconds samplingPeriod,
-                              std::chrono::microseconds maxBatchReportLatency) = 0;
-
-    /* Disable sensor in input reader mapper. */
-    virtual void disableSensor(int32_t deviceId, InputDeviceSensorType sensorType) = 0;
-
-    /* Flush sensor data in input reader mapper. */
-    virtual void flushSensor(int32_t deviceId, InputDeviceSensorType sensorType) = 0;
-
-    /* Set color for the light */
-    virtual bool setLightColor(int32_t deviceId, int32_t lightId, int32_t color) = 0;
-    /* Set player ID for the light */
-    virtual bool setLightPlayerId(int32_t deviceId, int32_t lightId, int32_t playerId) = 0;
-    /* Get light color */
-    virtual std::optional<int32_t> getLightColor(int32_t deviceId, int32_t lightId) = 0;
-    /* Get light player ID */
-    virtual std::optional<int32_t> getLightPlayerId(int32_t deviceId, int32_t lightId) = 0;
-
-    /* Get the Bluetooth address of an input device, if known. */
-    virtual std::optional<std::string> getBluetoothAddress(int32_t deviceId) const = 0;
-};
-
 // --- InputReaderConfiguration ---
 
 /*
@@ -160,51 +51,51 @@
  */
 struct InputReaderConfiguration {
     // Describes changes that have occurred.
-    enum {
+    enum class Change : uint32_t {
         // The mouse pointer speed changed.
-        CHANGE_POINTER_SPEED = 1 << 0,
+        POINTER_SPEED = 1u << 0,
 
         // The pointer gesture control changed.
-        CHANGE_POINTER_GESTURE_ENABLEMENT = 1 << 1,
+        POINTER_GESTURE_ENABLEMENT = 1u << 1,
 
         // The display size or orientation changed.
-        CHANGE_DISPLAY_INFO = 1 << 2,
+        DISPLAY_INFO = 1u << 2,
 
         // The visible touches option changed.
-        CHANGE_SHOW_TOUCHES = 1 << 3,
+        SHOW_TOUCHES = 1u << 3,
 
         // The keyboard layouts must be reloaded.
-        CHANGE_KEYBOARD_LAYOUTS = 1 << 4,
+        KEYBOARD_LAYOUTS = 1u << 4,
 
         // The device name alias supplied by the may have changed for some devices.
-        CHANGE_DEVICE_ALIAS = 1 << 5,
+        DEVICE_ALIAS = 1u << 5,
 
         // The location calibration matrix changed.
-        CHANGE_TOUCH_AFFINE_TRANSFORMATION = 1 << 6,
+        TOUCH_AFFINE_TRANSFORMATION = 1u << 6,
 
         // The presence of an external stylus has changed.
-        CHANGE_EXTERNAL_STYLUS_PRESENCE = 1 << 7,
+        EXTERNAL_STYLUS_PRESENCE = 1u << 7,
 
         // The pointer capture mode has changed.
-        CHANGE_POINTER_CAPTURE = 1 << 8,
+        POINTER_CAPTURE = 1u << 8,
 
         // The set of disabled input devices (disabledDevices) has changed.
-        CHANGE_ENABLED_STATE = 1 << 9,
+        ENABLED_STATE = 1u << 9,
 
         // The device type has been updated.
-        CHANGE_DEVICE_TYPE = 1 << 10,
+        DEVICE_TYPE = 1u << 10,
 
         // The keyboard layout association has changed.
-        CHANGE_KEYBOARD_LAYOUT_ASSOCIATION = 1 << 11,
+        KEYBOARD_LAYOUT_ASSOCIATION = 1u << 11,
 
         // The stylus button reporting configurations has changed.
-        CHANGE_STYLUS_BUTTON_REPORTING = 1 << 12,
+        STYLUS_BUTTON_REPORTING = 1u << 12,
 
         // The touchpad settings changed.
-        CHANGE_TOUCHPAD_SETTINGS = 1 << 13,
+        TOUCHPAD_SETTINGS = 1u << 13,
 
         // All devices must be reopened.
-        CHANGE_MUST_REOPEN = 1 << 31,
+        MUST_REOPEN = 1u << 31,
     };
 
     // Gets the amount of time to disable virtual keys after the screen is touched
@@ -364,8 +255,6 @@
             stylusButtonMotionEventsEnabled(true),
             stylusPointerIconEnabled(false) {}
 
-    static std::string changesToString(uint32_t changes);
-
     std::optional<DisplayViewport> getDisplayViewportByType(ViewportType type) const;
     std::optional<DisplayViewport> getDisplayViewportByUniqueId(const std::string& uniqueDisplayId)
             const;
@@ -380,6 +269,116 @@
     std::vector<DisplayViewport> mDisplays;
 };
 
+using ConfigurationChanges = ftl::Flags<InputReaderConfiguration::Change>;
+
+// --- InputReaderInterface ---
+
+/* The interface for the InputReader shared library.
+ *
+ * Manages one or more threads that process raw input events and sends cooked event data to an
+ * input listener.
+ *
+ * The implementation must guarantee thread safety for this interface. However, since the input
+ * listener is NOT thread safe, all calls to the listener must happen from the same thread.
+ */
+class InputReaderInterface {
+public:
+    InputReaderInterface() {}
+    virtual ~InputReaderInterface() {}
+    /* Dumps the state of the input reader.
+     *
+     * This method may be called on any thread (usually by the input manager). */
+    virtual void dump(std::string& dump) = 0;
+
+    /* Called by the heartbeat to ensures that the reader has not deadlocked. */
+    virtual void monitor() = 0;
+
+    /* Returns true if the input device is enabled. */
+    virtual bool isInputDeviceEnabled(int32_t deviceId) = 0;
+
+    /* Makes the reader start processing events from the kernel. */
+    virtual status_t start() = 0;
+
+    /* Makes the reader stop processing any more events. */
+    virtual status_t stop() = 0;
+
+    /* Gets information about all input devices.
+     *
+     * This method may be called on any thread (usually by the input manager).
+     */
+    virtual std::vector<InputDeviceInfo> getInputDevices() const = 0;
+
+    /* Query current input state. */
+    virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask, int32_t scanCode) = 0;
+    virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask, int32_t keyCode) = 0;
+    virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t sw) = 0;
+
+    virtual void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode,
+                                 int32_t toKeyCode) const = 0;
+
+    virtual int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const = 0;
+
+    /* Toggle Caps Lock */
+    virtual void toggleCapsLockState(int32_t deviceId) = 0;
+
+    /* Determine whether physical keys exist for the given framework-domain key codes. */
+    virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask,
+                         const std::vector<int32_t>& keyCodes, uint8_t* outFlags) = 0;
+
+    /* Requests that a reconfiguration of all input devices.
+     * The changes flag is a bitfield that indicates what has changed and whether
+     * the input devices must all be reopened. */
+    virtual void requestRefreshConfiguration(ConfigurationChanges changes) = 0;
+
+    /* Controls the vibrator of a particular input device. */
+    virtual void vibrate(int32_t deviceId, const VibrationSequence& sequence, ssize_t repeat,
+                         int32_t token) = 0;
+    virtual void cancelVibrate(int32_t deviceId, int32_t token) = 0;
+
+    virtual bool isVibrating(int32_t deviceId) = 0;
+
+    virtual std::vector<int32_t> getVibratorIds(int32_t deviceId) = 0;
+    /* Get battery level of a particular input device. */
+    virtual std::optional<int32_t> getBatteryCapacity(int32_t deviceId) = 0;
+    /* Get battery status of a particular input device. */
+    virtual std::optional<int32_t> getBatteryStatus(int32_t deviceId) = 0;
+    /* Get the device path for the battery of an input device. */
+    virtual std::optional<std::string> getBatteryDevicePath(int32_t deviceId) = 0;
+
+    virtual std::vector<InputDeviceLightInfo> getLights(int32_t deviceId) = 0;
+
+    virtual std::vector<InputDeviceSensorInfo> getSensors(int32_t deviceId) = 0;
+
+    /* Return true if the device can send input events to the specified display. */
+    virtual bool canDispatchToDisplay(int32_t deviceId, int32_t displayId) = 0;
+
+    /* Enable sensor in input reader mapper. */
+    virtual bool enableSensor(int32_t deviceId, InputDeviceSensorType sensorType,
+                              std::chrono::microseconds samplingPeriod,
+                              std::chrono::microseconds maxBatchReportLatency) = 0;
+
+    /* Disable sensor in input reader mapper. */
+    virtual void disableSensor(int32_t deviceId, InputDeviceSensorType sensorType) = 0;
+
+    /* Flush sensor data in input reader mapper. */
+    virtual void flushSensor(int32_t deviceId, InputDeviceSensorType sensorType) = 0;
+
+    /* Set color for the light */
+    virtual bool setLightColor(int32_t deviceId, int32_t lightId, int32_t color) = 0;
+    /* Set player ID for the light */
+    virtual bool setLightPlayerId(int32_t deviceId, int32_t lightId, int32_t playerId) = 0;
+    /* Get light color */
+    virtual std::optional<int32_t> getLightColor(int32_t deviceId, int32_t lightId) = 0;
+    /* Get light player ID */
+    virtual std::optional<int32_t> getLightPlayerId(int32_t deviceId, int32_t lightId) = 0;
+
+    /* Get the Bluetooth address of an input device, if known. */
+    virtual std::optional<std::string> getBluetoothAddress(int32_t deviceId) const = 0;
+
+    /* Sysfs node change reported. Recreate device if required to incorporate the new sysfs nodes */
+    virtual void sysfsNodeChanged(const std::string& sysfsNodePath) = 0;
+};
+
 // --- TouchAffineTransformation ---
 
 struct TouchAffineTransformation {
diff --git a/services/inputflinger/include/NotifyArgs.h b/services/inputflinger/include/NotifyArgs.h
index c46f905..7d29dd9 100644
--- a/services/inputflinger/include/NotifyArgs.h
+++ b/services/inputflinger/include/NotifyArgs.h
@@ -24,6 +24,21 @@
 
 namespace android {
 
+/* Describes a change in any of the connected input devices. */
+struct NotifyInputDevicesChangedArgs {
+    int32_t id;
+    std::vector<InputDeviceInfo> inputDeviceInfos;
+
+    inline NotifyInputDevicesChangedArgs() {}
+
+    NotifyInputDevicesChangedArgs(int32_t id, std::vector<InputDeviceInfo> infos);
+
+    bool operator==(const NotifyInputDevicesChangedArgs& rhs) const = default;
+
+    NotifyInputDevicesChangedArgs(const NotifyInputDevicesChangedArgs& other) = default;
+    NotifyInputDevicesChangedArgs& operator=(const NotifyInputDevicesChangedArgs&) = default;
+};
+
 /* Describes a configuration change event. */
 struct NotifyConfigurationChangedArgs {
     int32_t id;
@@ -36,6 +51,7 @@
     bool operator==(const NotifyConfigurationChangedArgs& rhs) const = default;
 
     NotifyConfigurationChangedArgs(const NotifyConfigurationChangedArgs& other) = default;
+    NotifyConfigurationChangedArgs& operator=(const NotifyConfigurationChangedArgs&) = default;
 };
 
 /* Describes a key event. */
@@ -65,6 +81,7 @@
     bool operator==(const NotifyKeyArgs& rhs) const = default;
 
     NotifyKeyArgs(const NotifyKeyArgs& other) = default;
+    NotifyKeyArgs& operator=(const NotifyKeyArgs&) = default;
 };
 
 /* Describes a motion event. */
@@ -115,7 +132,6 @@
                      const std::vector<TouchVideoFrame>& videoFrames);
 
     NotifyMotionArgs(const NotifyMotionArgs& other);
-
     NotifyMotionArgs& operator=(const android::NotifyMotionArgs&) = default;
 
     bool operator==(const NotifyMotionArgs& rhs) const;
@@ -143,6 +159,7 @@
                      bool accuracyChanged, nsecs_t hwTimestamp, std::vector<float> values);
 
     NotifySensorArgs(const NotifySensorArgs& other) = default;
+    NotifySensorArgs& operator=(const NotifySensorArgs&) = default;
 };
 
 /* Describes a switch event. */
@@ -160,6 +177,7 @@
                      uint32_t switchMask);
 
     NotifySwitchArgs(const NotifySwitchArgs& other) = default;
+    NotifySwitchArgs& operator=(const NotifySwitchArgs&) = default;
 
     bool operator==(const NotifySwitchArgs& rhs) const = default;
 };
@@ -177,13 +195,13 @@
     NotifyDeviceResetArgs(int32_t id, nsecs_t eventTime, int32_t deviceId);
 
     NotifyDeviceResetArgs(const NotifyDeviceResetArgs& other) = default;
+    NotifyDeviceResetArgs& operator=(const NotifyDeviceResetArgs&) = default;
 
     bool operator==(const NotifyDeviceResetArgs& rhs) const = default;
 };
 
 /* Describes a change in the state of Pointer Capture. */
 struct NotifyPointerCaptureChangedArgs {
-    // The sequence number of the Pointer Capture request, if enabled.
     int32_t id;
     nsecs_t eventTime;
 
@@ -194,6 +212,7 @@
     NotifyPointerCaptureChangedArgs(int32_t id, nsecs_t eventTime, const PointerCaptureRequest&);
 
     NotifyPointerCaptureChangedArgs(const NotifyPointerCaptureChangedArgs& other) = default;
+    NotifyPointerCaptureChangedArgs& operator=(const NotifyPointerCaptureChangedArgs&) = default;
 };
 
 /* Describes a vibrator state event. */
@@ -209,11 +228,13 @@
     NotifyVibratorStateArgs(int32_t id, nsecs_t eventTIme, int32_t deviceId, bool isOn);
 
     NotifyVibratorStateArgs(const NotifyVibratorStateArgs& other) = default;
+    NotifyVibratorStateArgs& operator=(const NotifyVibratorStateArgs&) = default;
 };
 
-using NotifyArgs = std::variant<NotifyConfigurationChangedArgs, NotifyKeyArgs, NotifyMotionArgs,
-                                NotifySensorArgs, NotifySwitchArgs, NotifyDeviceResetArgs,
-                                NotifyPointerCaptureChangedArgs, NotifyVibratorStateArgs>;
+using NotifyArgs =
+        std::variant<NotifyInputDevicesChangedArgs, NotifyConfigurationChangedArgs, NotifyKeyArgs,
+                     NotifyMotionArgs, NotifySensorArgs, NotifySwitchArgs, NotifyDeviceResetArgs,
+                     NotifyPointerCaptureChangedArgs, NotifyVibratorStateArgs>;
 
 const char* toString(const NotifyArgs& args);
 
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/include/UnwantedInteractionBlockerInterface.h b/services/inputflinger/include/UnwantedInteractionBlockerInterface.h
index 1a6f847..64c6114 100644
--- a/services/inputflinger/include/UnwantedInteractionBlockerInterface.h
+++ b/services/inputflinger/include/UnwantedInteractionBlockerInterface.h
@@ -27,23 +27,13 @@
  */
 class UnwantedInteractionBlockerInterface : public InputListenerInterface {
 public:
-    /* Notifies the input reader policy that some input devices have changed
-     * and provides information about all current input devices.
-     * Important! This call should happen on the same thread as the calls to the
-     * InputListenerInterface methods.
-     * That is, same thread should call 'notifyMotion' and 'notifyInputDevicesChanged' and
-     * 'notifyDeviceReset'. If this architecture changes, we will need to make the implementation
-     * of this interface thread-safe.
-     */
-    virtual void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) = 0;
-
     /**
      * Dump the state of the interaction blocker.
      * This method may be called on any thread (usually by the input manager on a binder thread).
      */
     virtual void dump(std::string& dump) = 0;
 
-    /* Called by the heatbeat to ensures that the blocker has not deadlocked. */
+    /* Called by the heartbeat to ensures that the blocker has not deadlocked. */
     virtual void monitor() = 0;
 
     UnwantedInteractionBlockerInterface() {}
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index 132c3a1..b0edb57 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -42,6 +42,7 @@
         "Macros.cpp",
         "TouchVideoDevice.cpp",
         "controller/PeripheralController.cpp",
+        "mapper/CapturedTouchpadEventConverter.cpp",
         "mapper/CursorInputMapper.cpp",
         "mapper/ExternalStylusInputMapper.cpp",
         "mapper/InputMapper.cpp",
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index d3d8021..0354164 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>
 
@@ -633,6 +634,11 @@
         }
     }
 
+    std::vector<int32_t> usageCodes = keyMap.keyLayoutMap->findUsageCodesForKey(keycode);
+    if (usageCodes.size() > 0 && mscBitmask.test(MSC_SCAN)) {
+        return true;
+    }
+
     return false;
 }
 
@@ -673,9 +679,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 +689,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 +888,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,
@@ -1067,16 +1072,8 @@
     Device* device = getDeviceLocked(deviceId);
     if (device != nullptr && device->keyMap.haveKeyLayout()) {
         for (size_t codeIndex = 0; codeIndex < keyCodes.size(); codeIndex++) {
-            std::vector<int32_t> scanCodes =
-                    device->keyMap.keyLayoutMap->findScanCodesForKey(keyCodes[codeIndex]);
-
-            // check the possible scan codes identified by the layout map against the
-            // map of codes actually emitted by the driver
-            for (const int32_t scanCode : scanCodes) {
-                if (device->keyBitmask.test(scanCode)) {
-                    outFlags[codeIndex] = 1;
-                    break;
-                }
+            if (device->hasKeycodeLocked(keyCodes[codeIndex])) {
+                outFlags[codeIndex] = 1;
             }
         }
         return true;
@@ -1531,6 +1528,20 @@
     return associatedDevice;
 }
 
+bool EventHub::AssociatedDevice::isChanged() const {
+    std::unordered_map<int32_t, RawBatteryInfo> newBatteryInfos =
+            readBatteryConfiguration(sysfsRootPath);
+    std::unordered_map<int32_t, RawLightInfo> newLightInfos =
+            readLightsConfiguration(sysfsRootPath);
+    std::optional<RawLayoutInfo> newLayoutInfo = readLayoutConfiguration(sysfsRootPath);
+
+    if (newBatteryInfos == batteryInfos && newLightInfos == lightInfos &&
+        newLayoutInfo == layoutInfo) {
+        return false;
+    }
+    return true;
+}
+
 void EventHub::vibrate(int32_t deviceId, const VibrationElement& element) {
     std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
@@ -2282,8 +2293,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") {
@@ -2536,6 +2547,42 @@
     return device->disable();
 }
 
+// TODO(b/274755573): Shift to uevent handling on native side and remove this method
+// Currently using Java UEventObserver to trigger this which uses UEvent infrastructure that uses a
+// NETLINK socket to observe UEvents. We can create similar infrastructure on Eventhub side to
+// directly observe UEvents instead of triggering from Java side.
+void EventHub::sysfsNodeChanged(const std::string& sysfsNodePath) {
+    std::scoped_lock _l(mLock);
+
+    // Check in opening devices
+    for (auto it = mOpeningDevices.begin(); it != mOpeningDevices.end(); it++) {
+        std::unique_ptr<Device>& device = *it;
+        if (device->associatedDevice &&
+            sysfsNodePath.find(device->associatedDevice->sysfsRootPath.string()) !=
+                    std::string::npos &&
+            device->associatedDevice->isChanged()) {
+            it = mOpeningDevices.erase(it);
+            openDeviceLocked(device->path);
+        }
+    }
+
+    // Check in already added device
+    std::vector<Device*> devicesToReopen;
+    for (const auto& [id, device] : mDevices) {
+        if (device->associatedDevice &&
+            sysfsNodePath.find(device->associatedDevice->sysfsRootPath.string()) !=
+                    std::string::npos &&
+            device->associatedDevice->isChanged()) {
+            devicesToReopen.push_back(device.get());
+        }
+    }
+    for (const auto& device : devicesToReopen) {
+        closeDeviceLocked(*device);
+        openDeviceLocked(device->path);
+    }
+    devicesToReopen.clear();
+}
+
 void EventHub::createVirtualKeyboardLocked() {
     InputDeviceIdentifier identifier;
     identifier.name = "Virtual";
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 9fe652c..c8c5115 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -146,99 +146,23 @@
     }
 }
 
-void InputDevice::addEventHubDevice(int32_t eventHubId, bool populateMappers) {
+void InputDevice::addEmptyEventHubDevice(int32_t eventHubId) {
     if (mDevices.find(eventHubId) != mDevices.end()) {
         return;
     }
     std::unique_ptr<InputDeviceContext> contextPtr(new InputDeviceContext(*this, eventHubId));
-    ftl::Flags<InputDeviceClass> classes = contextPtr->getDeviceClasses();
     std::vector<std::unique_ptr<InputMapper>> mappers;
 
-    // Check if we should skip population
-    if (!populateMappers) {
-        mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))});
+    mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))});
+}
+
+void InputDevice::addEventHubDevice(int32_t eventHubId,
+                                    const InputReaderConfiguration& readerConfig) {
+    if (mDevices.find(eventHubId) != mDevices.end()) {
         return;
     }
-
-    // Switch-like devices.
-    if (classes.test(InputDeviceClass::SWITCH)) {
-        mappers.push_back(std::make_unique<SwitchInputMapper>(*contextPtr));
-    }
-
-    // Scroll wheel-like devices.
-    if (classes.test(InputDeviceClass::ROTARY_ENCODER)) {
-        mappers.push_back(std::make_unique<RotaryEncoderInputMapper>(*contextPtr));
-    }
-
-    // Vibrator-like devices.
-    if (classes.test(InputDeviceClass::VIBRATOR)) {
-        mappers.push_back(std::make_unique<VibratorInputMapper>(*contextPtr));
-    }
-
-    // Battery-like devices or light-containing devices.
-    // PeripheralController will be created with associated EventHub device.
-    if (classes.test(InputDeviceClass::BATTERY) || classes.test(InputDeviceClass::LIGHT)) {
-        mController = std::make_unique<PeripheralController>(*contextPtr);
-    }
-
-    // Keyboard-like devices.
-    uint32_t keyboardSource = 0;
-    int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC;
-    if (classes.test(InputDeviceClass::KEYBOARD)) {
-        keyboardSource |= AINPUT_SOURCE_KEYBOARD;
-    }
-    if (classes.test(InputDeviceClass::ALPHAKEY)) {
-        keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC;
-    }
-    if (classes.test(InputDeviceClass::DPAD)) {
-        keyboardSource |= AINPUT_SOURCE_DPAD;
-    }
-    if (classes.test(InputDeviceClass::GAMEPAD)) {
-        keyboardSource |= AINPUT_SOURCE_GAMEPAD;
-    }
-
-    if (keyboardSource != 0) {
-        mappers.push_back(
-                std::make_unique<KeyboardInputMapper>(*contextPtr, keyboardSource, keyboardType));
-    }
-
-    // Cursor-like devices.
-    if (classes.test(InputDeviceClass::CURSOR)) {
-        mappers.push_back(std::make_unique<CursorInputMapper>(*contextPtr));
-    }
-
-    // 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.
-    const InputDeviceIdentifier identifier = contextPtr->getDeviceIdentifier();
-    const bool isSonyGamepadTouchpad = identifier.vendor == 0x054c &&
-            (identifier.product == 0x05c4 || identifier.product == 0x09cc ||
-             identifier.product == 0x0ce6);
-    if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) &&
-        classes.test(InputDeviceClass::TOUCH_MT) && !isSonyGamepadTouchpad) {
-        mappers.push_back(std::make_unique<TouchpadInputMapper>(*contextPtr));
-    } else if (classes.test(InputDeviceClass::TOUCH_MT)) {
-        mappers.push_back(std::make_unique<MultiTouchInputMapper>(*contextPtr));
-    } else if (classes.test(InputDeviceClass::TOUCH)) {
-        mappers.push_back(std::make_unique<SingleTouchInputMapper>(*contextPtr));
-    }
-
-    // Joystick-like devices.
-    if (classes.test(InputDeviceClass::JOYSTICK)) {
-        mappers.push_back(std::make_unique<JoystickInputMapper>(*contextPtr));
-    }
-
-    // Motion sensor enabled devices.
-    if (classes.test(InputDeviceClass::SENSOR)) {
-        mappers.push_back(std::make_unique<SensorInputMapper>(*contextPtr));
-    }
-
-    // External stylus-like devices.
-    if (classes.test(InputDeviceClass::EXTERNAL_STYLUS)) {
-        mappers.push_back(std::make_unique<ExternalStylusInputMapper>(*contextPtr));
-    }
+    std::unique_ptr<InputDeviceContext> contextPtr(new InputDeviceContext(*this, eventHubId));
+    std::vector<std::unique_ptr<InputMapper>> mappers = createMappers(*contextPtr, readerConfig);
 
     // insert the context into the devices set
     mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))});
@@ -254,8 +178,9 @@
     mDevices.erase(eventHubId);
 }
 
-std::list<NotifyArgs> InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config,
-                                             uint32_t changes) {
+std::list<NotifyArgs> InputDevice::configure(nsecs_t when,
+                                             const InputReaderConfiguration& readerConfig,
+                                             ConfigurationChanges changes) {
     std::list<NotifyArgs> out;
     mSources = 0;
     mClasses = ftl::Flags<InputDeviceClass>(0);
@@ -276,24 +201,28 @@
     mIsExternal = mClasses.test(InputDeviceClass::EXTERNAL);
     mHasMic = mClasses.test(InputDeviceClass::MIC);
 
-    if (!isIgnored()) {
+    using Change = InputReaderConfiguration::Change;
+
+    if (!changes.any() || !isIgnored()) {
         // Full configuration should happen the first time configure is called
         // and when the device type is changed. Changing a device type can
         // affect various other parameters so should result in a
         // reconfiguration.
-        if (!changes || (changes & InputReaderConfiguration::CHANGE_DEVICE_TYPE)) {
+        if (!changes.any() || changes.test(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 =
-                    getValueByKey(config->deviceTypeAssociations, mIdentifier.location);
+                    getValueByKey(readerConfig.deviceTypeAssociations, mIdentifier.location);
         }
 
-        if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUTS)) {
+        if (!changes.any() || changes.test(Change::KEYBOARD_LAYOUTS)) {
             if (!mClasses.test(InputDeviceClass::VIRTUAL)) {
                 std::shared_ptr<KeyCharacterMap> keyboardLayout =
                         mContext->getPolicy()->getKeyboardLayoutOverlay(mIdentifier);
@@ -310,7 +239,7 @@
             }
         }
 
-        if (!changes || (changes & InputReaderConfiguration::CHANGE_DEVICE_ALIAS)) {
+        if (!changes.any() || changes.test(Change::DEVICE_ALIAS)) {
             if (!(mClasses.test(InputDeviceClass::VIRTUAL))) {
                 std::string alias = mContext->getPolicy()->getDeviceAlias(mIdentifier);
                 if (mAlias != alias) {
@@ -320,16 +249,16 @@
             }
         }
 
-        if (changes & InputReaderConfiguration::CHANGE_ENABLED_STATE) {
+        if (changes.test(Change::ENABLED_STATE)) {
             // Do not execute this code on the first configure, because 'setEnabled' would call
             // InputMapper::reset, and you can't reset a mapper before it has been configured.
             // The mappers are configured for the first time at the bottom of this function.
-            auto it = config->disabledDevices.find(mId);
-            bool enabled = it == config->disabledDevices.end();
+            auto it = readerConfig.disabledDevices.find(mId);
+            bool enabled = it == readerConfig.disabledDevices.end();
             out += setEnabled(enabled, when);
         }
 
-        if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
+        if (!changes.any() || changes.test(Change::DISPLAY_INFO)) {
             // In most situations, no port or name will be specified.
             mAssociatedDisplayPort = std::nullopt;
             mAssociatedDisplayUniqueId = std::nullopt;
@@ -337,13 +266,14 @@
             // Find the display port that corresponds to the current input port.
             const std::string& inputPort = mIdentifier.location;
             if (!inputPort.empty()) {
-                const std::unordered_map<std::string, uint8_t>& ports = config->portAssociations;
+                const std::unordered_map<std::string, uint8_t>& ports =
+                        readerConfig.portAssociations;
                 const auto& displayPort = ports.find(inputPort);
                 if (displayPort != ports.end()) {
                     mAssociatedDisplayPort = std::make_optional(displayPort->second);
                 } else {
                     const std::unordered_map<std::string, std::string>& displayUniqueIds =
-                            config->uniqueIdAssociations;
+                            readerConfig.uniqueIdAssociations;
                     const auto& displayUniqueId = displayUniqueIds.find(inputPort);
                     if (displayUniqueId != displayUniqueIds.end()) {
                         mAssociatedDisplayUniqueId = displayUniqueId->second;
@@ -355,9 +285,11 @@
             // "disabledDevices" list. If it is associated with a specific display, and it was not
             // explicitly disabled, then enable/disable the device based on whether we can find the
             // corresponding viewport.
-            bool enabled = (config->disabledDevices.find(mId) == config->disabledDevices.end());
+            bool enabled =
+                    (readerConfig.disabledDevices.find(mId) == readerConfig.disabledDevices.end());
             if (mAssociatedDisplayPort) {
-                mAssociatedViewport = config->getDisplayViewportByPort(*mAssociatedDisplayPort);
+                mAssociatedViewport =
+                        readerConfig.getDisplayViewportByPort(*mAssociatedDisplayPort);
                 if (!mAssociatedViewport) {
                     ALOGW("Input device %s should be associated with display on port %" PRIu8 ", "
                           "but the corresponding viewport is not found.",
@@ -366,7 +298,7 @@
                 }
             } else if (mAssociatedDisplayUniqueId != std::nullopt) {
                 mAssociatedViewport =
-                        config->getDisplayViewportByUniqueId(*mAssociatedDisplayUniqueId);
+                        readerConfig.getDisplayViewportByUniqueId(*mAssociatedDisplayUniqueId);
                 if (!mAssociatedViewport) {
                     ALOGW("Input device %s should be associated with display %s but the "
                           "corresponding viewport cannot be found",
@@ -375,7 +307,7 @@
                 }
             }
 
-            if (changes) {
+            if (changes.any()) {
                 // For first-time configuration, only allow device to be disabled after mappers have
                 // finished configuring. This is because we need to read some of the properties from
                 // the device's open fd.
@@ -383,15 +315,16 @@
             }
         }
 
-        for_each_mapper([this, when, &config, changes, &out](InputMapper& mapper) {
-            out += mapper.configure(when, config, changes);
+        for_each_mapper([this, when, &readerConfig, changes, &out](InputMapper& mapper) {
+            out += mapper.reconfigure(when, readerConfig, changes);
             mSources |= mapper.getSources();
         });
 
         // If a device is just plugged but it might be disabled, we need to update some info like
         // axis range of touch from each InputMapper first, then disable it.
-        if (!changes) {
-            out += setEnabled(config->disabledDevices.find(mId) == config->disabledDevices.end(),
+        if (!changes.any()) {
+            out += setEnabled(readerConfig.disabledDevices.find(mId) ==
+                                      readerConfig.disabledDevices.end(),
                               when);
         }
     }
@@ -464,7 +397,7 @@
                              mHasMic, getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE));
 
     for_each_mapper(
-            [&outDeviceInfo](InputMapper& mapper) { mapper.populateDeviceInfo(&outDeviceInfo); });
+            [&outDeviceInfo](InputMapper& mapper) { mapper.populateDeviceInfo(outDeviceInfo); });
 
     if (mController) {
         mController->populateDeviceInfo(&outDeviceInfo);
@@ -506,6 +439,92 @@
     return result;
 }
 
+std::vector<std::unique_ptr<InputMapper>> InputDevice::createMappers(
+        InputDeviceContext& contextPtr, const InputReaderConfiguration& readerConfig) {
+    ftl::Flags<InputDeviceClass> classes = contextPtr.getDeviceClasses();
+    std::vector<std::unique_ptr<InputMapper>> mappers;
+
+    // Switch-like devices.
+    if (classes.test(InputDeviceClass::SWITCH)) {
+        mappers.push_back(createInputMapper<SwitchInputMapper>(contextPtr, readerConfig));
+    }
+
+    // Scroll wheel-like devices.
+    if (classes.test(InputDeviceClass::ROTARY_ENCODER)) {
+        mappers.push_back(createInputMapper<RotaryEncoderInputMapper>(contextPtr, readerConfig));
+    }
+
+    // Vibrator-like devices.
+    if (classes.test(InputDeviceClass::VIBRATOR)) {
+        mappers.push_back(createInputMapper<VibratorInputMapper>(contextPtr, readerConfig));
+    }
+
+    // Battery-like devices or light-containing devices.
+    // PeripheralController will be created with associated EventHub device.
+    if (classes.test(InputDeviceClass::BATTERY) || classes.test(InputDeviceClass::LIGHT)) {
+        mController = std::make_unique<PeripheralController>(contextPtr);
+    }
+
+    // Keyboard-like devices.
+    uint32_t keyboardSource = 0;
+    int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC;
+    if (classes.test(InputDeviceClass::KEYBOARD)) {
+        keyboardSource |= AINPUT_SOURCE_KEYBOARD;
+    }
+    if (classes.test(InputDeviceClass::ALPHAKEY)) {
+        keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC;
+    }
+    if (classes.test(InputDeviceClass::DPAD)) {
+        keyboardSource |= AINPUT_SOURCE_DPAD;
+    }
+    if (classes.test(InputDeviceClass::GAMEPAD)) {
+        keyboardSource |= AINPUT_SOURCE_GAMEPAD;
+    }
+
+    if (keyboardSource != 0) {
+        mappers.push_back(std::make_unique<KeyboardInputMapper>(contextPtr, readerConfig,
+                                                                keyboardSource, keyboardType));
+    }
+
+    // Cursor-like devices.
+    if (classes.test(InputDeviceClass::CURSOR)) {
+        mappers.push_back(createInputMapper<CursorInputMapper>(contextPtr, readerConfig));
+    }
+
+    // Touchscreens and touchpad devices.
+    static const bool ENABLE_TOUCHPAD_GESTURES_LIBRARY =
+            sysprop::InputProperties::enable_touchpad_gestures_library().value_or(true);
+    // 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 isSonyDualShock4Touchpad = identifier.vendor == 0x054c &&
+            (identifier.product == 0x05c4 || identifier.product == 0x09cc);
+    if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) &&
+        classes.test(InputDeviceClass::TOUCH_MT) && !isSonyDualShock4Touchpad) {
+        mappers.push_back(createInputMapper<TouchpadInputMapper>(contextPtr, readerConfig));
+    } else if (classes.test(InputDeviceClass::TOUCH_MT)) {
+        mappers.push_back(std::make_unique<MultiTouchInputMapper>(contextPtr, readerConfig));
+    } else if (classes.test(InputDeviceClass::TOUCH)) {
+        mappers.push_back(std::make_unique<SingleTouchInputMapper>(contextPtr, readerConfig));
+    }
+
+    // Joystick-like devices.
+    if (classes.test(InputDeviceClass::JOYSTICK)) {
+        mappers.push_back(createInputMapper<JoystickInputMapper>(contextPtr, readerConfig));
+    }
+
+    // Motion sensor enabled devices.
+    if (classes.test(InputDeviceClass::SENSOR)) {
+        mappers.push_back(createInputMapper<SensorInputMapper>(contextPtr, readerConfig));
+    }
+
+    // External stylus-like devices.
+    if (classes.test(InputDeviceClass::EXTERNAL_STYLUS)) {
+        mappers.push_back(createInputMapper<ExternalStylusInputMapper>(contextPtr, readerConfig));
+    }
+    return mappers;
+}
+
 bool InputDevice::markSupportedKeyCodes(uint32_t sourceMask, const std::vector<int32_t>& keyCodes,
                                         uint8_t* outFlags) {
     bool result = false;
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index 9080cc1..ea95f78 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -85,7 +85,7 @@
         mDisableVirtualKeysTimeout(LLONG_MIN),
         mNextTimeout(LLONG_MAX),
         mConfigurationChangesToRefresh(0) {
-    refreshConfigurationLocked(0);
+    refreshConfigurationLocked(/*changes=*/{});
     updateGlobalMetaStateLocked();
 }
 
@@ -122,9 +122,9 @@
         oldGeneration = mGeneration;
         timeoutMillis = -1;
 
-        uint32_t changes = mConfigurationChangesToRefresh;
-        if (changes) {
-            mConfigurationChangesToRefresh = 0;
+        auto changes = mConfigurationChangesToRefresh;
+        if (changes.any()) {
+            mConfigurationChangesToRefresh.clear();
             timeoutMillis = 0;
             refreshConfigurationLocked(changes);
         } else if (mNextTimeout != LLONG_MAX) {
@@ -157,6 +157,8 @@
         if (oldGeneration != mGeneration) {
             inputDevicesChanged = true;
             inputDevices = getInputDevicesLocked();
+            notifyArgs.emplace_back(
+                    NotifyInputDevicesChangedArgs{mContext.getNextId(), inputDevices});
         }
     } // release lock
 
@@ -234,7 +236,7 @@
     InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(eventHubId);
     std::shared_ptr<InputDevice> device = createDeviceLocked(eventHubId, identifier);
 
-    notifyAll(device->configure(when, &mConfig, 0));
+    notifyAll(device->configure(when, mConfig, /*changes=*/{}));
     notifyAll(device->reset(when));
 
     if (device->isIgnored()) {
@@ -310,7 +312,7 @@
 
     std::list<NotifyArgs> resetEvents;
     if (device->hasEventHubDevices()) {
-        resetEvents += device->configure(when, &mConfig, 0);
+        resetEvents += device->configure(when, mConfig, /*changes=*/{});
     }
     resetEvents += device->reset(when);
     notifyAll(std::move(resetEvents));
@@ -332,7 +334,7 @@
         device = std::make_shared<InputDevice>(&mContext, deviceId, bumpGenerationLocked(),
                                                identifier);
     }
-    device->addEventHubDevice(eventHubId);
+    device->addEventHubDevice(eventHubId, mConfig);
     return device;
 }
 
@@ -385,42 +387,40 @@
     updateGlobalMetaStateLocked();
 
     // Enqueue configuration changed.
-    NotifyConfigurationChangedArgs args(mContext.getNextId(), when);
-    mQueuedListener.notifyConfigurationChanged(&args);
+    mQueuedListener.notifyConfigurationChanged({mContext.getNextId(), when});
 }
 
-void InputReader::refreshConfigurationLocked(uint32_t changes) {
+void InputReader::refreshConfigurationLocked(ConfigurationChanges changes) {
     mPolicy->getReaderConfiguration(&mConfig);
     mEventHub->setExcludedDevices(mConfig.excludedDeviceNames);
 
-    if (!changes) return;
+    using Change = InputReaderConfiguration::Change;
+    if (!changes.any()) return;
 
-    ALOGI("Reconfiguring input devices, changes=%s",
-          InputReaderConfiguration::changesToString(changes).c_str());
+    ALOGI("Reconfiguring input devices, changes=%s", changes.string().c_str());
     nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
 
-    if (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO) {
+    if (changes.test(Change::DISPLAY_INFO)) {
         updatePointerDisplayLocked();
     }
 
-    if (changes & InputReaderConfiguration::CHANGE_MUST_REOPEN) {
+    if (changes.test(Change::MUST_REOPEN)) {
         mEventHub->requestReopenDevices();
     } else {
         for (auto& devicePair : mDevices) {
             std::shared_ptr<InputDevice>& device = devicePair.second;
-            notifyAll(device->configure(now, &mConfig, changes));
+            notifyAll(device->configure(now, mConfig, changes));
         }
     }
 
-    if (changes & InputReaderConfiguration::CHANGE_POINTER_CAPTURE) {
+    if (changes.test(Change::POINTER_CAPTURE)) {
         if (mCurrentPointerCaptureRequest == mConfig.pointerCaptureRequest) {
             ALOGV("Skipping notifying pointer capture changes: "
                   "There was no change in the pointer capture state.");
         } else {
             mCurrentPointerCaptureRequest = mConfig.pointerCaptureRequest;
-            const NotifyPointerCaptureChangedArgs args(mContext.getNextId(), now,
-                                                       mCurrentPointerCaptureRequest);
-            mQueuedListener.notifyPointerCaptureChanged(&args);
+            mQueuedListener.notifyPointerCaptureChanged(
+                    {mContext.getNextId(), now, mCurrentPointerCaptureRequest});
         }
     }
 }
@@ -457,7 +457,7 @@
 }
 
 void InputReader::notifyExternalStylusPresenceChangedLocked() {
-    refreshConfigurationLocked(InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE);
+    refreshConfigurationLocked(InputReaderConfiguration::Change::EXTERNAL_STYLUS_PRESENCE);
 }
 
 void InputReader::getExternalStylusDevicesLocked(std::vector<InputDeviceInfo>& outDevices) {
@@ -671,11 +671,11 @@
     return device->getKeyCodeForKeyLocation(locationKeyCode);
 }
 
-void InputReader::requestRefreshConfiguration(uint32_t changes) {
+void InputReader::requestRefreshConfiguration(ConfigurationChanges changes) {
     std::scoped_lock _l(mLock);
 
-    if (changes) {
-        bool needWake = !mConfigurationChangesToRefresh;
+    if (changes.any()) {
+        bool needWake = !mConfigurationChangesToRefresh.any();
         mConfigurationChangesToRefresh |= changes;
 
         if (needWake) {
@@ -928,6 +928,10 @@
     return *associatedDisplayId == displayId;
 }
 
+void InputReader::sysfsNodeChanged(const std::string& sysfsNodePath) {
+    mEventHub->sysfsNodeChanged(sysfsNodePath);
+}
+
 void InputReader::dump(std::string& dump) {
     std::scoped_lock _l(mLock);
 
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 86acadb..20612c7 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;
@@ -382,6 +388,10 @@
 
     /* Disable an input device. Closes file descriptor to that device. */
     virtual status_t disableDevice(int32_t deviceId) = 0;
+
+    /* Sysfs node changed. Reopen the Eventhub device if any new Peripheral like Light, Battery,
+     * etc. is detected. */
+    virtual void sysfsNodeChanged(const std::string& sysfsNodePath) = 0;
 };
 
 template <std::size_t BITS>
@@ -464,7 +474,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;
@@ -561,6 +571,8 @@
 
     status_t disableDevice(int32_t deviceId) override final;
 
+    void sysfsNodeChanged(const std::string& sysfsNodePath) override final;
+
     ~EventHub() override;
 
 private:
@@ -572,6 +584,7 @@
         std::unordered_map<int32_t /*lightId*/, RawLightInfo> lightInfos;
         std::optional<RawLayoutInfo> layoutInfo;
 
+        bool isChanged() const;
         bool operator==(const AssociatedDevice&) const = default;
         bool operator!=(const AssociatedDevice&) const = default;
         std::string dump() const;
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 7867029..0b8a608 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -80,11 +80,12 @@
     [[nodiscard]] std::list<NotifyArgs> setEnabled(bool enabled, nsecs_t when);
 
     void dump(std::string& dump, const std::string& eventHubDevStr);
-    void addEventHubDevice(int32_t eventHubId, bool populateMappers = true);
+    void addEmptyEventHubDevice(int32_t eventHubId);
+    void addEventHubDevice(int32_t eventHubId, const InputReaderConfiguration& readerConfig);
     void removeEventHubDevice(int32_t eventHubId);
     [[nodiscard]] std::list<NotifyArgs> configure(nsecs_t when,
-                                                  const InputReaderConfiguration* config,
-                                                  uint32_t changes);
+                                                  const InputReaderConfiguration& readerConfig,
+                                                  ConfigurationChanges changes);
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when);
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvents, size_t count);
     [[nodiscard]] std::list<NotifyArgs> timeoutExpired(nsecs_t when);
@@ -137,7 +138,7 @@
     template <class T, typename... Args>
     T& addMapper(int32_t eventHubId, Args... args) {
         // ensure a device entry exists for this eventHubId
-        addEventHubDevice(eventHubId, false);
+        addEmptyEventHubDevice(eventHubId);
 
         // create mapper
         auto& devicePair = mDevices[eventHubId];
@@ -148,11 +149,21 @@
         return *mapper;
     }
 
+    template <class T, typename... Args>
+    T& constructAndAddMapper(int32_t eventHubId, Args... args) {
+        // create mapper
+        auto& devicePair = mDevices[eventHubId];
+        auto& deviceContext = devicePair.first;
+        auto& mappers = devicePair.second;
+        mappers.push_back(createInputMapper<T>(*deviceContext, args...));
+        return static_cast<T&>(*mappers.back());
+    }
+
     // construct and add a controller to the input device
     template <class T>
     T& addController(int32_t eventHubId) {
         // ensure a device entry exists for this eventHubId
-        addEventHubDevice(eventHubId, false);
+        addEmptyEventHubDevice(eventHubId);
 
         // create controller
         auto& devicePair = mDevices[eventHubId];
@@ -191,6 +202,9 @@
     typedef int32_t (InputMapper::*GetStateFunc)(uint32_t sourceMask, int32_t code);
     int32_t getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc);
 
+    std::vector<std::unique_ptr<InputMapper>> createMappers(
+            InputDeviceContext& contextPtr, const InputReaderConfiguration& readerConfig);
+
     PropertyMap mConfiguration;
 
     // helpers to interate over the devices collection
@@ -269,9 +283,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);
     }
@@ -406,7 +417,7 @@
     inline const std::string getName() const { return mDevice.getName(); }
     inline const std::string getDescriptor() { return mDevice.getDescriptor(); }
     inline const std::string getLocation() { return mDevice.getLocation(); }
-    inline bool isExternal() { return mDevice.isExternal(); }
+    inline bool isExternal() const { return mDevice.isExternal(); }
     inline std::optional<uint8_t> getAssociatedDisplayPort() const {
         return mDevice.getAssociatedDisplayPort();
     }
@@ -423,7 +434,7 @@
         return mDevice.cancelTouch(when, readTime);
     }
     inline void bumpGeneration() { mDevice.bumpGeneration(); }
-    inline const PropertyMap& getConfiguration() { return mDevice.getConfiguration(); }
+    inline const PropertyMap& getConfiguration() const { return mDevice.getConfiguration(); }
 
 private:
     InputDevice& mDevice;
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index e9c989a..9112913 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -77,7 +77,7 @@
     bool hasKeys(int32_t deviceId, uint32_t sourceMask, const std::vector<int32_t>& keyCodes,
                  uint8_t* outFlags) override;
 
-    void requestRefreshConfiguration(uint32_t changes) override;
+    void requestRefreshConfiguration(ConfigurationChanges changes) override;
 
     void vibrate(int32_t deviceId, const VibrationSequence& sequence, ssize_t repeat,
                  int32_t token) override;
@@ -117,6 +117,8 @@
 
     std::optional<std::string> getBluetoothAddress(int32_t deviceId) const override;
 
+    void sysfsNodeChanged(const std::string& sysfsNodePath) override;
+
 protected:
     // These members are protected so they can be instrumented by test cases.
     virtual std::shared_ptr<InputDevice> createDeviceLocked(int32_t deviceId,
@@ -231,8 +233,8 @@
     nsecs_t mNextTimeout GUARDED_BY(mLock);
     void requestTimeoutAtTimeLocked(nsecs_t when) REQUIRES(mLock);
 
-    uint32_t mConfigurationChangesToRefresh GUARDED_BY(mLock);
-    void refreshConfigurationLocked(uint32_t changes) REQUIRES(mLock);
+    ConfigurationChanges mConfigurationChangesToRefresh GUARDED_BY(mLock);
+    void refreshConfigurationLocked(ConfigurationChanges changes) REQUIRES(mLock);
 
     void notifyAll(std::list<NotifyArgs>&& argsList);
 
diff --git a/services/inputflinger/reader/include/StylusState.h b/services/inputflinger/reader/include/StylusState.h
index ff15e0c..d042784 100644
--- a/services/inputflinger/reader/include/StylusState.h
+++ b/services/inputflinger/reader/include/StylusState.h
@@ -33,8 +33,8 @@
     std::optional<float> pressure{};
     /* The state of the stylus buttons as a bitfield (e.g. AMOTION_EVENT_BUTTON_SECONDARY). */
     uint32_t buttons{};
-    /* Which tool type the stylus is currently using (e.g. AMOTION_EVENT_TOOL_TYPE_ERASER). */
-    int32_t toolType{AMOTION_EVENT_TOOL_TYPE_UNKNOWN};
+    /* Which tool type the stylus is currently using (e.g. ToolType::ERASER). */
+    ToolType toolType{ToolType::UNKNOWN};
 
     void clear() { *this = StylusState{}; }
 };
diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
new file mode 100644
index 0000000..061c6a3
--- /dev/null
+++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
@@ -0,0 +1,328 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "CapturedTouchpadEventConverter.h"
+
+#include <sstream>
+
+#include <android-base/stringprintf.h>
+#include <gui/constants.h>
+#include <input/PrintTools.h>
+#include <linux/input-event-codes.h>
+#include <log/log_main.h>
+
+namespace android {
+
+namespace {
+
+int32_t actionWithIndex(int32_t action, int32_t index) {
+    return action | (index << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+}
+
+template <typename T>
+size_t firstUnmarkedBit(T set) {
+    // TODO: replace with std::countr_one from <bit> when that's available
+    LOG_ALWAYS_FATAL_IF(set.all());
+    size_t i = 0;
+    while (set.test(i)) {
+        i++;
+    }
+    return i;
+}
+
+} // namespace
+
+CapturedTouchpadEventConverter::CapturedTouchpadEventConverter(
+        InputReaderContext& readerContext, const InputDeviceContext& deviceContext,
+        MultiTouchMotionAccumulator& motionAccumulator, int32_t deviceId)
+      : mDeviceId(deviceId),
+        mReaderContext(readerContext),
+        mDeviceContext(deviceContext),
+        mMotionAccumulator(motionAccumulator),
+        mHasTouchMinor(deviceContext.hasAbsoluteAxis(ABS_MT_TOUCH_MINOR)),
+        mHasToolMinor(deviceContext.hasAbsoluteAxis(ABS_MT_WIDTH_MINOR)) {
+    RawAbsoluteAxisInfo orientationInfo;
+    deviceContext.getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &orientationInfo);
+    if (orientationInfo.valid) {
+        if (orientationInfo.maxValue > 0) {
+            mOrientationScale = M_PI_2 / orientationInfo.maxValue;
+        } else if (orientationInfo.minValue < 0) {
+            mOrientationScale = -M_PI_2 / orientationInfo.minValue;
+        }
+    }
+
+    // TODO(b/275369880): support touch.pressure.calibration and .scale properties when captured.
+    RawAbsoluteAxisInfo pressureInfo;
+    deviceContext.getAbsoluteAxisInfo(ABS_MT_PRESSURE, &pressureInfo);
+    if (pressureInfo.valid && pressureInfo.maxValue > 0) {
+        mPressureScale = 1.0 / pressureInfo.maxValue;
+    }
+
+    RawAbsoluteAxisInfo touchMajorInfo, toolMajorInfo;
+    deviceContext.getAbsoluteAxisInfo(ABS_MT_TOUCH_MAJOR, &touchMajorInfo);
+    deviceContext.getAbsoluteAxisInfo(ABS_MT_WIDTH_MAJOR, &toolMajorInfo);
+    mHasTouchMajor = touchMajorInfo.valid;
+    mHasToolMajor = toolMajorInfo.valid;
+    if (mHasTouchMajor && touchMajorInfo.maxValue != 0) {
+        mSizeScale = 1.0f / touchMajorInfo.maxValue;
+    } else if (mHasToolMajor && toolMajorInfo.maxValue != 0) {
+        mSizeScale = 1.0f / toolMajorInfo.maxValue;
+    }
+}
+
+std::string CapturedTouchpadEventConverter::dump() const {
+    std::stringstream out;
+    out << "Orientation scale: " << mOrientationScale << "\n";
+    out << "Pressure scale: " << mPressureScale << "\n";
+    out << "Size scale: " << mSizeScale << "\n";
+
+    out << "Dimension axes:";
+    if (mHasTouchMajor) out << " touch major";
+    if (mHasTouchMinor) out << ", touch minor";
+    if (mHasToolMajor) out << ", tool major";
+    if (mHasToolMinor) out << ", tool minor";
+    out << "\n";
+
+    out << "Down time: " << mDownTime << "\n";
+    out << StringPrintf("Button state: 0x%08x\n", mButtonState);
+
+    out << StringPrintf("Pointer IDs in use: %s\n", mPointerIdsInUse.to_string().c_str());
+
+    out << "Pointer IDs for slot numbers:\n";
+    out << addLinePrefix(dumpMap(mPointerIdForSlotNumber), "  ") << "\n";
+    return out.str();
+}
+
+void CapturedTouchpadEventConverter::populateMotionRanges(InputDeviceInfo& info) const {
+    tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_X, ABS_MT_POSITION_X);
+    tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_Y, ABS_MT_POSITION_Y);
+    tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOUCH_MAJOR, ABS_MT_TOUCH_MAJOR);
+    tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOUCH_MINOR, ABS_MT_TOUCH_MINOR);
+    tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOOL_MAJOR, ABS_MT_WIDTH_MAJOR);
+    tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOOL_MINOR, ABS_MT_WIDTH_MINOR);
+
+    RawAbsoluteAxisInfo pressureInfo;
+    mDeviceContext.getAbsoluteAxisInfo(ABS_MT_PRESSURE, &pressureInfo);
+    if (pressureInfo.valid) {
+        info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, SOURCE, 0, 1, 0, 0, 0);
+    }
+
+    RawAbsoluteAxisInfo orientationInfo;
+    mDeviceContext.getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &orientationInfo);
+    if (orientationInfo.valid && (orientationInfo.maxValue > 0 || orientationInfo.minValue < 0)) {
+        info.addMotionRange(AMOTION_EVENT_AXIS_ORIENTATION, SOURCE, -M_PI_2, M_PI_2, 0, 0, 0);
+    }
+
+    if (mHasTouchMajor || mHasToolMajor) {
+        info.addMotionRange(AMOTION_EVENT_AXIS_SIZE, SOURCE, 0, 1, 0, 0, 0);
+    }
+}
+
+void CapturedTouchpadEventConverter::tryAddRawMotionRange(InputDeviceInfo& deviceInfo,
+                                                          int32_t androidAxis,
+                                                          int32_t evdevAxis) const {
+    RawAbsoluteAxisInfo info;
+    mDeviceContext.getAbsoluteAxisInfo(evdevAxis, &info);
+    if (info.valid) {
+        deviceInfo.addMotionRange(androidAxis, SOURCE, info.minValue, info.maxValue, info.flat,
+                                  info.fuzz, info.resolution);
+    }
+}
+
+void CapturedTouchpadEventConverter::reset() {
+    mCursorButtonAccumulator.reset(mDeviceContext);
+    mDownTime = 0;
+    mPointerIdsInUse.reset();
+    mPointerIdForSlotNumber.clear();
+}
+
+std::list<NotifyArgs> CapturedTouchpadEventConverter::process(const RawEvent& rawEvent) {
+    std::list<NotifyArgs> out;
+    if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) {
+        out = sync(rawEvent.when, rawEvent.readTime);
+        mMotionAccumulator.finishSync();
+    }
+
+    mCursorButtonAccumulator.process(&rawEvent);
+    mMotionAccumulator.process(&rawEvent);
+    return out;
+}
+
+std::list<NotifyArgs> CapturedTouchpadEventConverter::sync(nsecs_t when, nsecs_t readTime) {
+    std::list<NotifyArgs> out;
+    std::vector<PointerCoords> coords;
+    std::vector<PointerProperties> properties;
+    std::map<size_t, size_t> coordsIndexForSlotNumber;
+
+    // For all the touches that were already down, send a MOVE event with their updated coordinates.
+    // A convention of the MotionEvent API is that pointer coordinates in UP events match the
+    // pointer's coordinates from the previous MOVE, so we still include touches here even if
+    // they've been lifted in this evdev frame.
+    if (!mPointerIdForSlotNumber.empty()) {
+        for (const auto [slotNumber, pointerId] : mPointerIdForSlotNumber) {
+            // Note that we don't check whether the touch has actually moved — it's rare for a touch
+            // to stay perfectly still between frames, and if it does the worst that can happen is
+            // an extra MOVE event, so it's not worth the overhead of checking for changes.
+            coordsIndexForSlotNumber[slotNumber] = coords.size();
+            coords.push_back(makePointerCoordsForSlot(mMotionAccumulator.getSlot(slotNumber)));
+            properties.push_back({.id = pointerId, .toolType = ToolType::FINGER});
+        }
+        out.push_back(
+                makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, coords, properties));
+    }
+
+    std::vector<size_t> upSlots, downSlots;
+    for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) {
+        const MultiTouchMotionAccumulator::Slot& slot = mMotionAccumulator.getSlot(i);
+        // Some touchpads continue to report contacts even after they've identified them as palms.
+        // We don't currently have a way to mark these as palms when reporting to apps, so don't
+        // report them at all.
+        const bool isInUse = slot.isInUse() && slot.getToolType() != ToolType::PALM;
+        const bool wasInUse = mPointerIdForSlotNumber.find(i) != mPointerIdForSlotNumber.end();
+        if (isInUse && !wasInUse) {
+            downSlots.push_back(i);
+        } else if (!isInUse && wasInUse) {
+            upSlots.push_back(i);
+        }
+    }
+
+    // Send BUTTON_RELEASE events. (This has to happen before any UP events to avoid sending
+    // BUTTON_RELEASE events without any pointers.)
+    uint32_t newButtonState;
+    if (coords.size() - upSlots.size() + downSlots.size() == 0) {
+        // If there won't be any pointers down after this evdev sync, we won't be able to send
+        // button updates on their own, as motion events without pointers are invalid. To avoid
+        // erroneously reporting buttons being held for long periods, send BUTTON_RELEASE events for
+        // all pressed buttons when the last pointer is lifted.
+        //
+        // This also prevents us from sending BUTTON_PRESS events too early in the case of touchpads
+        // which report a button press one evdev sync before reporting a touch going down.
+        newButtonState = 0;
+    } else {
+        newButtonState = mCursorButtonAccumulator.getButtonState();
+    }
+    for (uint32_t button = 1; button <= AMOTION_EVENT_BUTTON_FORWARD; button <<= 1) {
+        if (!(newButtonState & button) && mButtonState & button) {
+            mButtonState &= ~button;
+            out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+                                         coords, properties, /*actionButton=*/button));
+        }
+    }
+
+    // For any touches that were lifted, send UP or POINTER_UP events.
+    for (size_t slotNumber : upSlots) {
+        const size_t indexToRemove = coordsIndexForSlotNumber.at(slotNumber);
+        const bool cancel = mMotionAccumulator.getSlot(slotNumber).getToolType() == ToolType::PALM;
+        int32_t action;
+        if (coords.size() == 1) {
+            action = cancel ? AMOTION_EVENT_ACTION_CANCEL : AMOTION_EVENT_ACTION_UP;
+        } else {
+            action = actionWithIndex(AMOTION_EVENT_ACTION_POINTER_UP, indexToRemove);
+        }
+        out.push_back(makeMotionArgs(when, readTime, action, coords, properties, /*actionButton=*/0,
+                                     /*flags=*/cancel ? AMOTION_EVENT_FLAG_CANCELED : 0));
+
+        freePointerIdForSlot(slotNumber);
+        coords.erase(coords.begin() + indexToRemove);
+        properties.erase(properties.begin() + indexToRemove);
+        // Now that we've removed some coords and properties, we might have to update the slot
+        // number to coords index mapping.
+        coordsIndexForSlotNumber.erase(slotNumber);
+        for (auto& [_, index] : coordsIndexForSlotNumber) {
+            if (index > indexToRemove) {
+                index--;
+            }
+        }
+    }
+
+    // For new touches, send DOWN or POINTER_DOWN events.
+    for (size_t slotNumber : downSlots) {
+        const size_t coordsIndex = coords.size();
+        const int32_t action = coords.empty()
+                ? AMOTION_EVENT_ACTION_DOWN
+                : actionWithIndex(AMOTION_EVENT_ACTION_POINTER_DOWN, coordsIndex);
+
+        coordsIndexForSlotNumber[slotNumber] = coordsIndex;
+        coords.push_back(makePointerCoordsForSlot(mMotionAccumulator.getSlot(slotNumber)));
+        properties.push_back(
+                {.id = allocatePointerIdToSlot(slotNumber), .toolType = ToolType::FINGER});
+
+        out.push_back(makeMotionArgs(when, readTime, action, coords, properties));
+    }
+
+    for (uint32_t button = 1; button <= AMOTION_EVENT_BUTTON_FORWARD; button <<= 1) {
+        if (newButtonState & button && !(mButtonState & button)) {
+            mButtonState |= button;
+            out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS, coords,
+                                         properties, /*actionButton=*/button));
+        }
+    }
+    return out;
+}
+
+NotifyMotionArgs CapturedTouchpadEventConverter::makeMotionArgs(
+        nsecs_t when, nsecs_t readTime, int32_t action, const std::vector<PointerCoords>& coords,
+        const std::vector<PointerProperties>& properties, int32_t actionButton, int32_t flags) {
+    LOG_ALWAYS_FATAL_IF(coords.size() != properties.size(),
+                        "Mismatched coords and properties arrays.");
+    return NotifyMotionArgs(mReaderContext.getNextId(), when, readTime, mDeviceId, SOURCE,
+                            ADISPLAY_ID_NONE, /*policyFlags=*/POLICY_FLAG_WAKE, action,
+                            /*actionButton=*/actionButton, flags,
+                            mReaderContext.getGlobalMetaState(), mButtonState,
+                            MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, coords.size(),
+                            properties.data(), coords.data(), /*xPrecision=*/1.0f,
+                            /*yPrecision=*/1.0f, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                            AMOTION_EVENT_INVALID_CURSOR_POSITION, mDownTime, /*videoFrames=*/{});
+}
+
+PointerCoords CapturedTouchpadEventConverter::makePointerCoordsForSlot(
+        const MultiTouchMotionAccumulator::Slot& slot) const {
+    PointerCoords coords;
+    coords.clear();
+    coords.setAxisValue(AMOTION_EVENT_AXIS_X, slot.getX());
+    coords.setAxisValue(AMOTION_EVENT_AXIS_Y, slot.getY());
+    coords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, slot.getTouchMajor());
+    coords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, slot.getTouchMinor());
+    coords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, slot.getToolMajor());
+    coords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, slot.getToolMinor());
+    coords.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, slot.getOrientation() * mOrientationScale);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, slot.getPressure() * mPressureScale);
+    float size = 0;
+    // TODO(b/275369880): support touch.size.calibration and .isSummed properties when captured.
+    if (mHasTouchMajor) {
+        size = mHasTouchMinor ? (slot.getTouchMajor() + slot.getTouchMinor()) / 2
+                              : slot.getTouchMajor();
+    } else if (mHasToolMajor) {
+        size = mHasToolMinor ? (slot.getToolMajor() + slot.getToolMinor()) / 2
+                             : slot.getToolMajor();
+    }
+    coords.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size * mSizeScale);
+    return coords;
+}
+
+int32_t CapturedTouchpadEventConverter::allocatePointerIdToSlot(size_t slotNumber) {
+    const int32_t pointerId = firstUnmarkedBit(mPointerIdsInUse);
+    mPointerIdsInUse.set(pointerId);
+    mPointerIdForSlotNumber[slotNumber] = pointerId;
+    return pointerId;
+}
+
+void CapturedTouchpadEventConverter::freePointerIdForSlot(size_t slotNumber) {
+    mPointerIdsInUse.reset(mPointerIdForSlotNumber.at(slotNumber));
+    mPointerIdForSlotNumber.erase(slotNumber);
+}
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h
new file mode 100644
index 0000000..9b6df7a
--- /dev/null
+++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h
@@ -0,0 +1,83 @@
+/*
+ * 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 <bitset>
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <android/input.h>
+#include <input/Input.h>
+#include <utils/Timers.h>
+
+#include "EventHub.h"
+#include "InputDevice.h"
+#include "accumulator/CursorButtonAccumulator.h"
+#include "accumulator/MultiTouchMotionAccumulator.h"
+#include "accumulator/TouchButtonAccumulator.h"
+
+namespace android {
+
+class CapturedTouchpadEventConverter {
+public:
+    explicit CapturedTouchpadEventConverter(InputReaderContext& readerContext,
+                                            const InputDeviceContext& deviceContext,
+                                            MultiTouchMotionAccumulator& motionAccumulator,
+                                            int32_t deviceId);
+    std::string dump() const;
+    void populateMotionRanges(InputDeviceInfo& info) const;
+    void reset();
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent);
+
+private:
+    void tryAddRawMotionRange(InputDeviceInfo& deviceInfo, int32_t androidAxis,
+                              int32_t evdevAxis) const;
+    [[nodiscard]] std::list<NotifyArgs> sync(nsecs_t when, nsecs_t readTime);
+    [[nodiscard]] NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
+                                                  const std::vector<PointerCoords>& coords,
+                                                  const std::vector<PointerProperties>& properties,
+                                                  int32_t actionButton = 0, int32_t flags = 0);
+    PointerCoords makePointerCoordsForSlot(const MultiTouchMotionAccumulator::Slot& slot) const;
+    int32_t allocatePointerIdToSlot(size_t slotNumber);
+    void freePointerIdForSlot(size_t slotNumber);
+
+    const int32_t mDeviceId;
+    InputReaderContext& mReaderContext;
+    const InputDeviceContext& mDeviceContext;
+    CursorButtonAccumulator mCursorButtonAccumulator;
+    MultiTouchMotionAccumulator& mMotionAccumulator;
+
+    float mOrientationScale = 0;
+    float mPressureScale = 1;
+    float mSizeScale = 0;
+    bool mHasTouchMajor;
+    const bool mHasTouchMinor;
+    bool mHasToolMajor;
+    const bool mHasToolMinor;
+    nsecs_t mDownTime = 0;
+    uint32_t mButtonState = 0;
+
+    std::bitset<MAX_POINTER_ID + 1> mPointerIdsInUse;
+    std::map<size_t, int32_t> mPointerIdForSlotNumber;
+
+    static constexpr uint32_t SOURCE = AINPUT_SOURCE_TOUCHPAD;
+};
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index 13e4d0c..c684ed4 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"
@@ -66,8 +68,10 @@
 
 // --- CursorInputMapper ---
 
-CursorInputMapper::CursorInputMapper(InputDeviceContext& deviceContext)
-      : InputMapper(deviceContext), mLastEventTime(std::numeric_limits<nsecs_t>::min()) {}
+CursorInputMapper::CursorInputMapper(InputDeviceContext& deviceContext,
+                                     const InputReaderConfiguration& readerConfig)
+      : InputMapper(deviceContext, readerConfig),
+        mLastEventTime(std::numeric_limits<nsecs_t>::min()) {}
 
 CursorInputMapper::~CursorInputMapper() {
     if (mPointerController != nullptr) {
@@ -79,30 +83,31 @@
     return mSource;
 }
 
-void CursorInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+void CursorInputMapper::populateDeviceInfo(InputDeviceInfo& info) {
     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);
-        info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale, 0.0f);
-        info->addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, mSource, -1.0f, 1.0f, 0.0f, mXScale,
-                             0.0f);
-        info->addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale,
-                             0.0f);
+        info.addMotionRange(AMOTION_EVENT_AXIS_X, mSource, -1.0f, 1.0f, 0.0f, mXScale, 0.0f);
+        info.addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale, 0.0f);
+        info.addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, mSource, -1.0f, 1.0f, 0.0f, mXScale,
+                            0.0f);
+        info.addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale,
+                            0.0f);
     }
-    info->addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, mSource, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f);
+    info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, mSource, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f);
 
     if (mCursorScrollAccumulator.haveRelativeVWheel()) {
-        info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
+        info.addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
     }
     if (mCursorScrollAccumulator.haveRelativeHWheel()) {
-        info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
+        info.addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
     }
 }
 
@@ -130,143 +135,56 @@
     dump += StringPrintf(INDENT3 "DownTime: %" PRId64 "\n", mDownTime);
 }
 
-std::list<NotifyArgs> CursorInputMapper::configure(nsecs_t when,
-                                                   const InputReaderConfiguration* config,
-                                                   uint32_t changes) {
-    std::list<NotifyArgs> out = InputMapper::configure(when, config, changes);
+std::list<NotifyArgs> CursorInputMapper::reconfigure(nsecs_t when,
+                                                     const InputReaderConfiguration& readerConfig,
+                                                     ConfigurationChanges changes) {
+    std::list<NotifyArgs> out = InputMapper::reconfigure(when, readerConfig, changes);
 
-    if (!changes) { // first time only
-        mCursorScrollAccumulator.configure(getDeviceContext());
-
-        // Configure basic parameters.
-        configureParameters();
-
-        // Configure device mode.
-        switch (mParameters.mode) {
-            case Parameters::Mode::POINTER_RELATIVE:
-                // Should not happen during first time configuration.
-                ALOGE("Cannot start a device in MODE_POINTER_RELATIVE, starting in MODE_POINTER");
-                mParameters.mode = Parameters::Mode::POINTER;
-                [[fallthrough]];
-            case Parameters::Mode::POINTER:
-                mSource = AINPUT_SOURCE_MOUSE;
-                mXPrecision = 1.0f;
-                mYPrecision = 1.0f;
-                mXScale = 1.0f;
-                mYScale = 1.0f;
-                mPointerController = getContext()->getPointerController(getDeviceId());
-                break;
-            case Parameters::Mode::NAVIGATION:
-                mSource = AINPUT_SOURCE_TRACKBALL;
-                mXPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
-                mYPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
-                mXScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
-                mYScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
-                break;
-        }
-
-        mVWheelScale = 1.0f;
-        mHWheelScale = 1.0f;
+    if (!changes.any()) { // first time only
+        configureBasicParams();
     }
 
-    const bool configurePointerCapture = mParameters.mode != Parameters::Mode::NAVIGATION &&
-            ((!changes && config->pointerCaptureRequest.enable) ||
-             (changes & InputReaderConfiguration::CHANGE_POINTER_CAPTURE));
+    const bool configurePointerCapture = !changes.any() ||
+            (mParameters.mode != Parameters::Mode::NAVIGATION &&
+             changes.test(InputReaderConfiguration::Change::POINTER_CAPTURE));
     if (configurePointerCapture) {
-        if (config->pointerCaptureRequest.enable) {
-            if (mParameters.mode == Parameters::Mode::POINTER) {
-                mParameters.mode = Parameters::Mode::POINTER_RELATIVE;
-                mSource = AINPUT_SOURCE_MOUSE_RELATIVE;
-                // Keep PointerController around in order to preserve the pointer position.
-                mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
-            } else {
-                ALOGE("Cannot request pointer capture, device is not in MODE_POINTER");
-            }
-        } else {
-            if (mParameters.mode == Parameters::Mode::POINTER_RELATIVE) {
-                mParameters.mode = Parameters::Mode::POINTER;
-                mSource = AINPUT_SOURCE_MOUSE;
-            } else {
-                ALOGE("Cannot release pointer capture, device is not in MODE_POINTER_RELATIVE");
-            }
-        }
-        bumpGeneration();
-        if (changes) {
-            out.push_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId()));
-        }
+        configureOnPointerCapture(readerConfig);
+        out.push_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId()));
     }
 
-    if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED) ||
+    if (!changes.any() || changes.test(InputReaderConfiguration::Change::POINTER_SPEED) ||
         configurePointerCapture) {
-        if (mParameters.mode == Parameters::Mode::POINTER_RELATIVE) {
-            // Disable any acceleration or scaling for the pointer when Pointer Capture is enabled.
-            mPointerVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
-            mWheelXVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
-            mWheelYVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
-        } else {
-            mPointerVelocityControl.setParameters(config->pointerVelocityControlParameters);
-            mWheelXVelocityControl.setParameters(config->wheelVelocityControlParameters);
-            mWheelYVelocityControl.setParameters(config->wheelVelocityControlParameters);
-        }
+        configureOnChangePointerSpeed(readerConfig);
     }
 
-    if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO) ||
+    if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO) ||
         configurePointerCapture) {
-        const bool isPointer = mParameters.mode == Parameters::Mode::POINTER;
-
-        mDisplayId = ADISPLAY_ID_NONE;
-        if (auto viewport = mDeviceContext.getAssociatedViewport(); viewport) {
-            // This InputDevice is associated with a viewport.
-            // Only generate events for the associated display.
-            const bool mismatchedPointerDisplay =
-                    isPointer && (viewport->displayId != mPointerController->getDisplayId());
-            mDisplayId = mismatchedPointerDisplay ? std::nullopt
-                                                  : std::make_optional(viewport->displayId);
-        } else if (isPointer) {
-            // The InputDevice is not associated with a viewport, but it controls the mouse pointer.
-            mDisplayId = mPointerController->getDisplayId();
-        }
-
-        mOrientation = ui::ROTATION_0;
-        const bool isOrientedDevice =
-                (mParameters.orientationAware && mParameters.hasAssociatedDisplay);
-        // InputReader works in the un-rotated display coordinate space, so we don't need to do
-        // anything if the device is already orientation-aware. If the device is not
-        // orientation-aware, then we need to apply the inverse rotation of the display so that
-        // when the display rotation is applied later as a part of the per-window transform, we
-        // get the expected screen coordinates. When pointer capture is enabled, we do not apply any
-        // rotations and report values directly from the input device.
-        if (!isOrientedDevice && mDisplayId &&
-            mParameters.mode != Parameters::Mode::POINTER_RELATIVE) {
-            if (auto viewport = config->getDisplayViewportById(*mDisplayId); viewport) {
-                mOrientation = getInverseRotation(viewport->orientation);
-            }
-        }
-
-        bumpGeneration();
+        configureOnChangeDisplayInfo(readerConfig);
     }
     return out;
 }
 
-void CursorInputMapper::configureParameters() {
-    mParameters.mode = Parameters::Mode::POINTER;
-    std::string cursorModeString;
-    if (getDeviceContext().getConfiguration().tryGetProperty("cursor.mode", cursorModeString)) {
-        if (cursorModeString == "navigation") {
-            mParameters.mode = Parameters::Mode::NAVIGATION;
-        } else if (cursorModeString != "pointer" && cursorModeString != "default") {
-            ALOGW("Invalid value for cursor.mode: '%s'", cursorModeString.c_str());
+CursorInputMapper::Parameters CursorInputMapper::computeParameters(
+        const InputDeviceContext& deviceContext) {
+    Parameters parameters;
+    parameters.mode = Parameters::Mode::POINTER;
+    const PropertyMap& config = deviceContext.getConfiguration();
+    std::optional<std::string> cursorModeString = config.getString("cursor.mode");
+    if (cursorModeString.has_value()) {
+        if (*cursorModeString == "navigation") {
+            parameters.mode = Parameters::Mode::NAVIGATION;
+        } 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);
+    parameters.orientationAware = config.getBool("cursor.orientationAware").value_or(false);
 
-    mParameters.hasAssociatedDisplay = false;
-    if (mParameters.mode == Parameters::Mode::POINTER || mParameters.orientationAware) {
-        mParameters.hasAssociatedDisplay = true;
+    parameters.hasAssociatedDisplay = false;
+    if (parameters.mode == Parameters::Mode::POINTER || parameters.orientationAware) {
+        parameters.hasAssociatedDisplay = true;
     }
+    return parameters;
 }
 
 void CursorInputMapper::dumpParameters(std::string& dump) {
@@ -348,7 +266,7 @@
     PointerProperties pointerProperties;
     pointerProperties.clear();
     pointerProperties.id = 0;
-    pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_MOUSE;
+    pointerProperties.toolType = ToolType::MOUSE;
 
     PointerCoords pointerCoords;
     pointerCoords.clear();
@@ -371,15 +289,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);
@@ -514,4 +427,106 @@
     return mDisplayId;
 }
 
+void CursorInputMapper::configureBasicParams() {
+    mCursorScrollAccumulator.configure(getDeviceContext());
+
+    // Configure basic parameters.
+    mParameters = computeParameters(getDeviceContext());
+
+    // Configure device mode.
+    switch (mParameters.mode) {
+        case Parameters::Mode::POINTER_RELATIVE:
+            // Should not happen during first time configuration.
+            ALOGE("Cannot start a device in MODE_POINTER_RELATIVE, starting in MODE_POINTER");
+            mParameters.mode = Parameters::Mode::POINTER;
+            [[fallthrough]];
+        case Parameters::Mode::POINTER:
+            mSource = AINPUT_SOURCE_MOUSE;
+            mXPrecision = 1.0f;
+            mYPrecision = 1.0f;
+            mXScale = 1.0f;
+            mYScale = 1.0f;
+            mPointerController = getContext()->getPointerController(getDeviceId());
+            break;
+        case Parameters::Mode::NAVIGATION:
+            mSource = AINPUT_SOURCE_TRACKBALL;
+            mXPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
+            mYPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
+            mXScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
+            mYScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
+            break;
+    }
+
+    mVWheelScale = 1.0f;
+    mHWheelScale = 1.0f;
+}
+
+void CursorInputMapper::configureOnPointerCapture(const InputReaderConfiguration& config) {
+    if (config.pointerCaptureRequest.enable) {
+        if (mParameters.mode == Parameters::Mode::POINTER) {
+            mParameters.mode = Parameters::Mode::POINTER_RELATIVE;
+            mSource = AINPUT_SOURCE_MOUSE_RELATIVE;
+            // Keep PointerController around in order to preserve the pointer position.
+            mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
+        } else {
+            ALOGE("Cannot request pointer capture, device is not in MODE_POINTER");
+        }
+    } else {
+        if (mParameters.mode == Parameters::Mode::POINTER_RELATIVE) {
+            mParameters.mode = Parameters::Mode::POINTER;
+            mSource = AINPUT_SOURCE_MOUSE;
+        } else {
+            ALOGE("Cannot release pointer capture, device is not in MODE_POINTER_RELATIVE");
+        }
+    }
+    bumpGeneration();
+}
+
+void CursorInputMapper::configureOnChangePointerSpeed(const InputReaderConfiguration& config) {
+    if (mParameters.mode == Parameters::Mode::POINTER_RELATIVE) {
+        // Disable any acceleration or scaling for the pointer when Pointer Capture is enabled.
+        mPointerVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
+        mWheelXVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
+        mWheelYVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
+    } else {
+        mPointerVelocityControl.setParameters(config.pointerVelocityControlParameters);
+        mWheelXVelocityControl.setParameters(config.wheelVelocityControlParameters);
+        mWheelYVelocityControl.setParameters(config.wheelVelocityControlParameters);
+    }
+}
+
+void CursorInputMapper::configureOnChangeDisplayInfo(const InputReaderConfiguration& config) {
+    const bool isPointer = mParameters.mode == Parameters::Mode::POINTER;
+
+    mDisplayId = ADISPLAY_ID_NONE;
+    if (auto viewport = mDeviceContext.getAssociatedViewport(); viewport) {
+        // This InputDevice is associated with a viewport.
+        // Only generate events for the associated display.
+        const bool mismatchedPointerDisplay =
+                isPointer && (viewport->displayId != mPointerController->getDisplayId());
+        mDisplayId =
+                mismatchedPointerDisplay ? std::nullopt : std::make_optional(viewport->displayId);
+    } else if (isPointer) {
+        // The InputDevice is not associated with a viewport, but it controls the mouse pointer.
+        mDisplayId = mPointerController->getDisplayId();
+    }
+
+    mOrientation = ui::ROTATION_0;
+    const bool isOrientedDevice =
+            (mParameters.orientationAware && mParameters.hasAssociatedDisplay);
+    // InputReader works in the un-rotated display coordinate space, so we don't need to do
+    // anything if the device is already orientation-aware. If the device is not
+    // orientation-aware, then we need to apply the inverse rotation of the display so that
+    // when the display rotation is applied later as a part of the per-window transform, we
+    // get the expected screen coordinates. When pointer capture is enabled, we do not apply any
+    // rotations and report values directly from the input device.
+    if (!isOrientedDevice && mDisplayId && mParameters.mode != Parameters::Mode::POINTER_RELATIVE) {
+        if (auto viewport = config.getDisplayViewportById(*mDisplayId); viewport) {
+            mOrientation = getInverseRotation(viewport->orientation);
+        }
+    }
+
+    bumpGeneration();
+}
+
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h
index 939cceb..b879bfd 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -53,15 +53,18 @@
 
 class CursorInputMapper : public InputMapper {
 public:
-    explicit CursorInputMapper(InputDeviceContext& deviceContext);
+    template <class T, class... Args>
+    friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
+                                                const InputReaderConfiguration& readerConfig,
+                                                Args... args);
     virtual ~CursorInputMapper();
 
     virtual uint32_t getSources() const override;
-    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
+    virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override;
     virtual void dump(std::string& dump) override;
-    [[nodiscard]] std::list<NotifyArgs> configure(nsecs_t when,
-                                                  const InputReaderConfiguration* config,
-                                                  uint32_t changes) override;
+    [[nodiscard]] std::list<NotifyArgs> reconfigure(nsecs_t when,
+                                                    const InputReaderConfiguration& readerConfig,
+                                                    ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
 
@@ -124,10 +127,17 @@
     nsecs_t mDownTime;
     nsecs_t mLastEventTime;
 
-    void configureParameters();
+    explicit CursorInputMapper(InputDeviceContext& deviceContext,
+                               const InputReaderConfiguration& readerConfig);
     void dumpParameters(std::string& dump);
+    void configureBasicParams();
+    void configureOnPointerCapture(const InputReaderConfiguration& config);
+    void configureOnChangePointerSpeed(const InputReaderConfiguration& config);
+    void configureOnChangeDisplayInfo(const InputReaderConfiguration& config);
 
     [[nodiscard]] std::list<NotifyArgs> sync(nsecs_t when, nsecs_t readTime);
+
+    static Parameters computeParameters(const InputDeviceContext& deviceContext);
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
index 2809939..987d2d0 100644
--- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
@@ -23,18 +23,19 @@
 
 namespace android {
 
-ExternalStylusInputMapper::ExternalStylusInputMapper(InputDeviceContext& deviceContext)
-      : InputMapper(deviceContext), mTouchButtonAccumulator(deviceContext) {}
+ExternalStylusInputMapper::ExternalStylusInputMapper(InputDeviceContext& deviceContext,
+                                                     const InputReaderConfiguration& readerConfig)
+      : InputMapper(deviceContext, readerConfig), mTouchButtonAccumulator(deviceContext) {}
 
 uint32_t ExternalStylusInputMapper::getSources() const {
     return AINPUT_SOURCE_STYLUS;
 }
 
-void ExternalStylusInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+void ExternalStylusInputMapper::populateDeviceInfo(InputDeviceInfo& info) {
     InputMapper::populateDeviceInfo(info);
     if (mRawPressureAxis.valid) {
-        info->addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, AINPUT_SOURCE_STYLUS, 0.0f, 1.0f, 0.0f,
-                             0.0f, 0.0f);
+        info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, AINPUT_SOURCE_STYLUS, 0.0f, 1.0f, 0.0f,
+                            0.0f, 0.0f);
     }
 }
 
@@ -46,9 +47,9 @@
     dumpStylusState(dump, mStylusState);
 }
 
-std::list<NotifyArgs> ExternalStylusInputMapper::configure(nsecs_t when,
-                                                           const InputReaderConfiguration* config,
-                                                           uint32_t changes) {
+std::list<NotifyArgs> ExternalStylusInputMapper::reconfigure(nsecs_t when,
+                                                             const InputReaderConfiguration& config,
+                                                             ConfigurationChanges changes) {
     getAbsoluteAxisInfo(ABS_PRESSURE, &mRawPressureAxis);
     mTouchButtonAccumulator.configure();
     return {};
@@ -77,8 +78,8 @@
     mStylusState.when = when;
 
     mStylusState.toolType = mTouchButtonAccumulator.getToolType();
-    if (mStylusState.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
-        mStylusState.toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    if (mStylusState.toolType == ToolType::UNKNOWN) {
+        mStylusState.toolType = ToolType::STYLUS;
     }
 
     if (mRawPressureAxis.valid) {
diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h
index b6c9055..97df02b 100644
--- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h
+++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h
@@ -26,15 +26,18 @@
 
 class ExternalStylusInputMapper : public InputMapper {
 public:
-    explicit ExternalStylusInputMapper(InputDeviceContext& deviceContext);
+    template <class T, class... Args>
+    friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
+                                                const InputReaderConfiguration& readerConfig,
+                                                Args... args);
     virtual ~ExternalStylusInputMapper() = default;
 
     uint32_t getSources() const override;
-    void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
+    void populateDeviceInfo(InputDeviceInfo& deviceInfo) override;
     void dump(std::string& dump) override;
-    [[nodiscard]] std::list<NotifyArgs> configure(nsecs_t when,
-                                                  const InputReaderConfiguration* config,
-                                                  uint32_t changes) override;
+    [[nodiscard]] std::list<NotifyArgs> reconfigure(nsecs_t when,
+                                                    const InputReaderConfiguration& config,
+                                                    ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
 
@@ -45,6 +48,8 @@
 
     StylusState mStylusState;
 
+    explicit ExternalStylusInputMapper(InputDeviceContext& deviceContext,
+                                       const InputReaderConfiguration& readerConfig);
     [[nodiscard]] std::list<NotifyArgs> sync(nsecs_t when);
 };
 
diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp
index ba2ea99..0692dbb 100644
--- a/services/inputflinger/reader/mapper/InputMapper.cpp
+++ b/services/inputflinger/reader/mapper/InputMapper.cpp
@@ -25,18 +25,20 @@
 
 namespace android {
 
-InputMapper::InputMapper(InputDeviceContext& deviceContext) : mDeviceContext(deviceContext) {}
+InputMapper::InputMapper(InputDeviceContext& deviceContext,
+                         const InputReaderConfiguration& readerConfig)
+      : mDeviceContext(deviceContext) {}
 
 InputMapper::~InputMapper() {}
 
-void InputMapper::populateDeviceInfo(InputDeviceInfo* info) {
-    info->addSource(getSources());
+void InputMapper::populateDeviceInfo(InputDeviceInfo& info) {
+    info.addSource(getSources());
 }
 
 void InputMapper::dump(std::string& dump) {}
 
-std::list<NotifyArgs> InputMapper::configure(nsecs_t when, const InputReaderConfiguration* config,
-                                             uint32_t changes) {
+std::list<NotifyArgs> InputMapper::reconfigure(nsecs_t when, const InputReaderConfiguration& config,
+                                               ConfigurationChanges changes) {
     return {};
 }
 
diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h
index 104305b..06de4c2 100644
--- a/services/inputflinger/reader/mapper/InputMapper.h
+++ b/services/inputflinger/reader/mapper/InputMapper.h
@@ -25,22 +25,43 @@
 #include "VibrationElement.h"
 
 namespace android {
+/**
+ * This is the factory method that must be used to create any InputMapper
+ */
+template <class T, class... Args>
+std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
+                                     const InputReaderConfiguration& readerConfig, Args... args) {
+    // Using `new` to access non-public constructors.
+    std::unique_ptr<T> mapper(new T(deviceContext, readerConfig, args...));
+    // We need to reset and configure the mapper to ensure it is ready to process event
+    nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+    std::list<NotifyArgs> unused = mapper->reset(now);
+    unused += mapper->reconfigure(now, readerConfig, /*changes=*/{});
+    return mapper;
+}
 
 /* An input mapper transforms raw input events into cooked event data.
  * A single input device can have multiple associated input mappers in order to interpret
  * different classes of events.
  *
  * InputMapper lifecycle:
- * - create
- * - configure with 0 changes
+ * - create and configure with 0 changes
  * - reset
- * - process, process, process (may occasionally reconfigure with non-zero changes or reset)
+ * - process, process, process (may occasionally reconfigure or reset)
  * - reset
  * - destroy
  */
 class InputMapper {
 public:
-    explicit InputMapper(InputDeviceContext& deviceContext);
+    /**
+     * Subclasses must either provide a public constructor
+     * or must be-friend the factory method.
+     */
+    template <class T, class... Args>
+    friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
+                                                const InputReaderConfiguration& readerConfig,
+                                                Args... args);
+
     virtual ~InputMapper();
 
     inline int32_t getDeviceId() { return mDeviceContext.getId(); }
@@ -51,11 +72,11 @@
     inline InputReaderPolicyInterface* getPolicy() { return getContext()->getPolicy(); }
 
     virtual uint32_t getSources() const = 0;
-    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo);
     virtual void dump(std::string& dump);
-    [[nodiscard]] virtual std::list<NotifyArgs> configure(nsecs_t when,
-                                                          const InputReaderConfiguration* config,
-                                                          uint32_t changes);
+    [[nodiscard]] virtual std::list<NotifyArgs> reconfigure(nsecs_t when,
+                                                            const InputReaderConfiguration& config,
+                                                            ConfigurationChanges changes);
     [[nodiscard]] virtual std::list<NotifyArgs> reset(nsecs_t when);
     [[nodiscard]] virtual std::list<NotifyArgs> process(const RawEvent* rawEvent) = 0;
     [[nodiscard]] virtual std::list<NotifyArgs> timeoutExpired(nsecs_t when);
@@ -102,6 +123,9 @@
 protected:
     InputDeviceContext& mDeviceContext;
 
+    explicit InputMapper(InputDeviceContext& deviceContext,
+                         const InputReaderConfiguration& readerConfig);
+
     status_t getAbsoluteAxisInfo(int32_t axis, RawAbsoluteAxisInfo* axisInfo);
     void bumpGeneration();
 
diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
index d7f8b17..099a955 100644
--- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
@@ -20,8 +20,9 @@
 
 namespace android {
 
-JoystickInputMapper::JoystickInputMapper(InputDeviceContext& deviceContext)
-      : InputMapper(deviceContext) {}
+JoystickInputMapper::JoystickInputMapper(InputDeviceContext& deviceContext,
+                                         const InputReaderConfiguration& readerConfig)
+      : InputMapper(deviceContext, readerConfig) {}
 
 JoystickInputMapper::~JoystickInputMapper() {}
 
@@ -29,7 +30,7 @@
     return AINPUT_SOURCE_JOYSTICK;
 }
 
-void JoystickInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+void JoystickInputMapper::populateDeviceInfo(InputDeviceInfo& info) {
     InputMapper::populateDeviceInfo(info);
 
     for (const auto& [_, axis] : mAxes) {
@@ -41,16 +42,16 @@
     }
 }
 
-void JoystickInputMapper::addMotionRange(int32_t axisId, const Axis& axis, InputDeviceInfo* info) {
-    info->addMotionRange(axisId, AINPUT_SOURCE_JOYSTICK, axis.min, axis.max, axis.flat, axis.fuzz,
-                         axis.resolution);
+void JoystickInputMapper::addMotionRange(int32_t axisId, const Axis& axis, InputDeviceInfo& info) {
+    info.addMotionRange(axisId, AINPUT_SOURCE_JOYSTICK, axis.min, axis.max, axis.flat, axis.fuzz,
+                        axis.resolution);
     /* In order to ease the transition for developers from using the old axes
      * to the newer, more semantically correct axes, we'll continue to register
      * the old axes as duplicates of their corresponding new ones.  */
     int32_t compatAxis = getCompatAxis(axisId);
     if (compatAxis >= 0) {
-        info->addMotionRange(compatAxis, AINPUT_SOURCE_JOYSTICK, axis.min, axis.max, axis.flat,
-                             axis.fuzz, axis.resolution);
+        info.addMotionRange(compatAxis, AINPUT_SOURCE_JOYSTICK, axis.min, axis.max, axis.flat,
+                            axis.fuzz, axis.resolution);
     }
 }
 
@@ -103,12 +104,12 @@
     }
 }
 
-std::list<NotifyArgs> JoystickInputMapper::configure(nsecs_t when,
-                                                     const InputReaderConfiguration* config,
-                                                     uint32_t changes) {
-    std::list<NotifyArgs> out = InputMapper::configure(when, config, changes);
+std::list<NotifyArgs> JoystickInputMapper::reconfigure(nsecs_t when,
+                                                       const InputReaderConfiguration& config,
+                                                       ConfigurationChanges changes) {
+    std::list<NotifyArgs> out = InputMapper::reconfigure(when, config, changes);
 
-    if (!changes) { // first time only
+    if (!changes.any()) { // first time only
         // Collect all axes.
         for (int32_t abs = 0; abs <= ABS_MAX; abs++) {
             if (!(getAbsAxisUsage(abs, getDeviceContext().getDeviceClasses())
@@ -321,7 +322,7 @@
     PointerProperties pointerProperties;
     pointerProperties.clear();
     pointerProperties.id = 0;
-    pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+    pointerProperties.toolType = ToolType::UNKNOWN;
 
     PointerCoords pointerCoords;
     pointerCoords.clear();
diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.h b/services/inputflinger/reader/mapper/JoystickInputMapper.h
index 72b8a52..313f092 100644
--- a/services/inputflinger/reader/mapper/JoystickInputMapper.h
+++ b/services/inputflinger/reader/mapper/JoystickInputMapper.h
@@ -22,15 +22,18 @@
 
 class JoystickInputMapper : public InputMapper {
 public:
-    explicit JoystickInputMapper(InputDeviceContext& deviceContext);
+    template <class T, class... Args>
+    friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
+                                                const InputReaderConfiguration& readerConfig,
+                                                Args... args);
     virtual ~JoystickInputMapper();
 
     virtual uint32_t getSources() const override;
-    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
+    virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override;
     virtual void dump(std::string& dump) override;
-    [[nodiscard]] std::list<NotifyArgs> configure(nsecs_t when,
-                                                  const InputReaderConfiguration* config,
-                                                  uint32_t changes) override;
+    [[nodiscard]] std::list<NotifyArgs> reconfigure(nsecs_t when,
+                                                    const InputReaderConfiguration& config,
+                                                    ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
 
@@ -86,6 +89,9 @@
         }
     };
 
+    explicit JoystickInputMapper(InputDeviceContext& deviceContext,
+                                 const InputReaderConfiguration& readerConfig);
+
     static Axis createAxis(const AxisInfo& AxisInfo, const RawAbsoluteAxisInfo& rawAxisInfo,
                            bool explicitlyMapped);
 
@@ -106,7 +112,7 @@
     static bool isCenteredAxis(int32_t axis);
     static int32_t getCompatAxis(int32_t axis);
 
-    static void addMotionRange(int32_t axisId, const Axis& axis, InputDeviceInfo* info);
+    static void addMotionRange(int32_t axisId, const Axis& axis, InputDeviceInfo& info);
     static void setPointerCoordsAxisValue(PointerCoords* pointerCoords, int32_t axis, float value);
 };
 
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index 0361bc6..7388752 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -61,41 +61,12 @@
             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,
-                                         int32_t keyboardType)
-      : InputMapper(deviceContext), mSource(source), mKeyboardType(keyboardType) {}
+KeyboardInputMapper::KeyboardInputMapper(InputDeviceContext& deviceContext,
+                                         const InputReaderConfiguration& readerConfig,
+                                         uint32_t source, int32_t keyboardType)
+      : InputMapper(deviceContext, readerConfig), mSource(source), mKeyboardType(keyboardType) {}
 
 uint32_t KeyboardInputMapper::getSources() const {
     return mSource;
@@ -115,18 +86,18 @@
     return ADISPLAY_ID_NONE;
 }
 
-void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo& info) {
     InputMapper::populateDeviceInfo(info);
 
-    info->setKeyboardType(mKeyboardType);
-    info->setKeyCharacterMap(getDeviceContext().getKeyCharacterMap());
+    info.setKeyboardType(mKeyboardType);
+    info.setKeyCharacterMap(getDeviceContext().getKeyCharacterMap());
 
     if (mKeyboardLayoutInfo) {
-        info->setKeyboardLayoutInfo(*mKeyboardLayoutInfo);
+        info.setKeyboardLayoutInfo(*mKeyboardLayoutInfo);
     } else {
         std::optional<RawLayoutInfo> layoutInfo = getDeviceContext().getRawLayoutInfo();
         if (layoutInfo) {
-            info->setKeyboardLayoutInfo(
+            info.setKeyboardLayoutInfo(
                     KeyboardLayoutInfo(layoutInfo->languageTag, layoutInfo->layoutType));
         }
     }
@@ -148,51 +119,51 @@
 }
 
 std::optional<DisplayViewport> KeyboardInputMapper::findViewport(
-        const InputReaderConfiguration* config) {
+        const InputReaderConfiguration& readerConfig) {
     if (getDeviceContext().getAssociatedViewport()) {
         return getDeviceContext().getAssociatedViewport();
     }
 
     // No associated display defined, try to find default display if orientationAware.
     if (mParameters.orientationAware) {
-        return config->getDisplayViewportByType(ViewportType::INTERNAL);
+        return readerConfig.getDisplayViewportByType(ViewportType::INTERNAL);
     }
 
     return std::nullopt;
 }
 
-std::list<NotifyArgs> KeyboardInputMapper::configure(nsecs_t when,
-                                                     const InputReaderConfiguration* config,
-                                                     uint32_t changes) {
-    std::list<NotifyArgs> out = InputMapper::configure(when, config, changes);
+std::list<NotifyArgs> KeyboardInputMapper::reconfigure(nsecs_t when,
+                                                       const InputReaderConfiguration& config,
+                                                       ConfigurationChanges changes) {
+    std::list<NotifyArgs> out = InputMapper::reconfigure(when, config, changes);
 
-    if (!changes) { // first time only
+    if (!changes.any()) { // first time only
         // Configure basic parameters.
         configureParameters();
     }
 
-    if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
+    if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) {
         mViewport = findViewport(config);
     }
 
-    if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUT_ASSOCIATION)) {
-        mKeyboardLayoutInfo =
-                getValueByKey(config->keyboardLayoutAssociations, getDeviceContext().getLocation());
+    if (!changes.any() ||
+        changes.test(InputReaderConfiguration::Change::KEYBOARD_LAYOUT_ASSOCIATION)) {
+        std::optional<KeyboardLayoutInfo> newKeyboardLayoutInfo =
+                getValueByKey(config.keyboardLayoutAssociations, getDeviceContext().getLocation());
+        if (mKeyboardLayoutInfo != newKeyboardLayoutInfo) {
+            mKeyboardLayoutInfo = newKeyboardLayoutInfo;
+            bumpGeneration();
+        }
     }
 
     return out;
 }
 
 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 +266,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/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
index da5b8ee..bd27383 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
@@ -23,15 +23,17 @@
 
 class KeyboardInputMapper : public InputMapper {
 public:
-    KeyboardInputMapper(InputDeviceContext& deviceContext, uint32_t source, int32_t keyboardType);
+    KeyboardInputMapper(InputDeviceContext& deviceContext,
+                        const InputReaderConfiguration& readerConfig, uint32_t source,
+                        int32_t keyboardType);
     ~KeyboardInputMapper() override = default;
 
     uint32_t getSources() const override;
-    void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
+    void populateDeviceInfo(InputDeviceInfo& deviceInfo) override;
     void dump(std::string& dump) override;
-    [[nodiscard]] std::list<NotifyArgs> configure(nsecs_t when,
-                                                  const InputReaderConfiguration* config,
-                                                  uint32_t changes) override;
+    [[nodiscard]] std::list<NotifyArgs> reconfigure(nsecs_t when,
+                                                    const InputReaderConfiguration& config,
+                                                    ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
 
@@ -96,7 +98,7 @@
     void resetLedState();
     void initializeLedState(LedState& ledState, int32_t led);
     void updateLedStateForModifier(LedState& ledState, int32_t led, int32_t modifier, bool reset);
-    std::optional<DisplayViewport> findViewport(const InputReaderConfiguration* config);
+    std::optional<DisplayViewport> findViewport(const InputReaderConfiguration& readerConfig);
     [[nodiscard]] std::list<NotifyArgs> cancelAllDownKeys(nsecs_t when);
 };
 
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index 33e72c7..9c87c62 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -28,8 +28,9 @@
 
 // --- MultiTouchInputMapper ---
 
-MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext)
-      : TouchInputMapper(deviceContext) {}
+MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext,
+                                             const InputReaderConfiguration& readerConfig)
+      : TouchInputMapper(deviceContext, readerConfig) {}
 
 MultiTouchInputMapper::~MultiTouchInputMapper() {}
 
@@ -77,7 +78,7 @@
             continue;
         }
 
-        if (inSlot.getToolType() == AMOTION_EVENT_TOOL_TYPE_PALM) {
+        if (inSlot.getToolType() == ToolType::PALM) {
             std::optional<int32_t> id = getActiveBitId(inSlot);
             if (id) {
                 outState->rawPointerData.canceledIdBits.markBit(id.value());
@@ -112,12 +113,12 @@
         outPointer.tiltY = 0;
 
         outPointer.toolType = inSlot.getToolType();
-        if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+        if (outPointer.toolType == ToolType::UNKNOWN) {
             outPointer.toolType = mTouchButtonAccumulator.getToolType();
-            if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
-                outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+            if (outPointer.toolType == ToolType::UNKNOWN) {
+                outPointer.toolType = ToolType::FINGER;
             }
-        } else if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS && !mStylusMtToolSeen) {
+        } else if (outPointer.toolType == ToolType::STYLUS && !mStylusMtToolSeen) {
             mStylusMtToolSeen = true;
             // The multi-touch device produced a stylus event with MT_TOOL_PEN. Dynamically
             // re-configure this input device so that we add SOURCE_STYLUS if we haven't already.
@@ -130,12 +131,11 @@
                 bumpGeneration();
             }
         }
-        if (shouldSimulateStylusWithTouch() &&
-            outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_FINGER) {
-            outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+        if (shouldSimulateStylusWithTouch() && outPointer.toolType == ToolType::FINGER) {
+            outPointer.toolType = ToolType::STYLUS;
         }
 
-        bool isHovering = mTouchButtonAccumulator.getToolType() != AMOTION_EVENT_TOOL_TYPE_MOUSE &&
+        bool isHovering = mTouchButtonAccumulator.getToolType() != ToolType::MOUSE &&
                 (mTouchButtonAccumulator.isHovering() ||
                  (mRawPointerAxes.pressure.valid && inSlot.getPressure() <= 0));
         outPointer.isHovering = isHovering;
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
index 5f8bccf..f300ee1 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
@@ -23,7 +23,13 @@
 
 class MultiTouchInputMapper : public TouchInputMapper {
 public:
-    explicit MultiTouchInputMapper(InputDeviceContext& deviceContext);
+    template <class T, class... Args>
+    friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
+                                                const InputReaderConfiguration& readerConfig,
+                                                Args... args);
+    explicit MultiTouchInputMapper(InputDeviceContext& deviceContext,
+                                   const InputReaderConfiguration& readerConfig);
+
     ~MultiTouchInputMapper() override;
 
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
index 19a79d7..13f2e59 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
@@ -20,12 +20,15 @@
 
 #include "RotaryEncoderInputMapper.h"
 
+#include <optional>
+
 #include "CursorScrollAccumulator.h"
 
 namespace android {
 
-RotaryEncoderInputMapper::RotaryEncoderInputMapper(InputDeviceContext& deviceContext)
-      : InputMapper(deviceContext), mOrientation(ui::ROTATION_0) {
+RotaryEncoderInputMapper::RotaryEncoderInputMapper(InputDeviceContext& deviceContext,
+                                                   const InputReaderConfiguration& readerConfig)
+      : InputMapper(deviceContext, readerConfig), mOrientation(ui::ROTATION_0) {
     mSource = AINPUT_SOURCE_ROTARY_ENCODER;
 }
 
@@ -35,22 +38,23 @@
     return mSource;
 }
 
-void RotaryEncoderInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+void RotaryEncoderInputMapper::populateDeviceInfo(InputDeviceInfo& info) {
     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;
         }
-        info->addMotionRange(AMOTION_EVENT_AXIS_SCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f,
-                             res * mScalingFactor);
+        mScalingFactor = scalingFactor.value_or(1.0f);
+        info.addMotionRange(AMOTION_EVENT_AXIS_SCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f,
+                            res.value_or(0.0f) * mScalingFactor);
     }
 }
 
@@ -60,16 +64,16 @@
                          toString(mRotaryEncoderScrollAccumulator.haveRelativeVWheel()));
 }
 
-std::list<NotifyArgs> RotaryEncoderInputMapper::configure(nsecs_t when,
-                                                          const InputReaderConfiguration* config,
-                                                          uint32_t changes) {
-    std::list<NotifyArgs> out = InputMapper::configure(when, config, changes);
-    if (!changes) {
+std::list<NotifyArgs> RotaryEncoderInputMapper::reconfigure(nsecs_t when,
+                                                            const InputReaderConfiguration& config,
+                                                            ConfigurationChanges changes) {
+    std::list<NotifyArgs> out = InputMapper::reconfigure(when, config, changes);
+    if (!changes.any()) {
         mRotaryEncoderScrollAccumulator.configure(getDeviceContext());
     }
-    if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
+    if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) {
         std::optional<DisplayViewport> internalViewport =
-                config->getDisplayViewportByType(ViewportType::INTERNAL);
+                config.getDisplayViewportByType(ViewportType::INTERNAL);
         if (internalViewport) {
             mOrientation = internalViewport->orientation;
         } else {
@@ -118,7 +122,7 @@
         PointerProperties pointerProperties;
         pointerProperties.clear();
         pointerProperties.id = 0;
-        pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+        pointerProperties.toolType = ToolType::UNKNOWN;
 
         uint32_t policyFlags = 0;
         if (getDeviceContext().isExternal()) {
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
index cb5fd88..9e2e8c4 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
@@ -25,15 +25,18 @@
 
 class RotaryEncoderInputMapper : public InputMapper {
 public:
-    explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext);
+    template <class T, class... Args>
+    friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
+                                                const InputReaderConfiguration& readerConfig,
+                                                Args... args);
     virtual ~RotaryEncoderInputMapper();
 
     virtual uint32_t getSources() const override;
-    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
+    virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override;
     virtual void dump(std::string& dump) override;
-    [[nodiscard]] std::list<NotifyArgs> configure(nsecs_t when,
-                                                  const InputReaderConfiguration* config,
-                                                  uint32_t changes) override;
+    [[nodiscard]] std::list<NotifyArgs> reconfigure(nsecs_t when,
+                                                    const InputReaderConfiguration& config,
+                                                    ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
 
@@ -44,6 +47,8 @@
     float mScalingFactor;
     ui::Rotation mOrientation;
 
+    explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext,
+                                      const InputReaderConfiguration& readerConfig);
     [[nodiscard]] std::list<NotifyArgs> sync(nsecs_t when, nsecs_t readTime);
 };
 
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
index f797bc9..a131e35 100644
--- a/services/inputflinger/reader/mapper/SensorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
@@ -52,8 +52,9 @@
     }
 }
 
-SensorInputMapper::SensorInputMapper(InputDeviceContext& deviceContext)
-      : InputMapper(deviceContext) {}
+SensorInputMapper::SensorInputMapper(InputDeviceContext& deviceContext,
+                                     const InputReaderConfiguration& readerConfig)
+      : InputMapper(deviceContext, readerConfig) {}
 
 SensorInputMapper::~SensorInputMapper() {}
 
@@ -61,12 +62,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);
@@ -79,12 +74,12 @@
     }
 }
 
-void SensorInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+void SensorInputMapper::populateDeviceInfo(InputDeviceInfo& info) {
     InputMapper::populateDeviceInfo(info);
 
     for (const auto& [sensorType, sensor] : mSensors) {
-        info->addSensorInfo(sensor.sensorInfo);
-        info->setHasSensor(true);
+        info.addSensorInfo(sensor.sensorInfo);
+        info.setHasSensor(true);
     }
 }
 
@@ -122,12 +117,12 @@
     }
 }
 
-std::list<NotifyArgs> SensorInputMapper::configure(nsecs_t when,
-                                                   const InputReaderConfiguration* config,
-                                                   uint32_t changes) {
-    std::list<NotifyArgs> out = InputMapper::configure(when, config, changes);
+std::list<NotifyArgs> SensorInputMapper::reconfigure(nsecs_t when,
+                                                     const InputReaderConfiguration& config,
+                                                     ConfigurationChanges changes) {
+    std::list<NotifyArgs> out = InputMapper::reconfigure(when, config, changes);
 
-    if (!changes) { // first time only
+    if (!changes.any()) { // first time only
         mDeviceEnabled = true;
         // Check if device has MSC_TIMESTAMP event.
         mHasHardwareTimestamp = getDeviceContext().hasMscEvent(MSC_TIMESTAMP);
@@ -201,6 +196,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 +215,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..a55dcd1 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 {
@@ -24,15 +27,18 @@
 
 class SensorInputMapper : public InputMapper {
 public:
-    explicit SensorInputMapper(InputDeviceContext& deviceContext);
+    template <class T, class... Args>
+    friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
+                                                const InputReaderConfiguration& readerConfig,
+                                                Args... args);
     ~SensorInputMapper() override;
 
     uint32_t getSources() const override;
-    void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
+    void populateDeviceInfo(InputDeviceInfo& deviceInfo) override;
     void dump(std::string& dump) override;
-    [[nodiscard]] std::list<NotifyArgs> configure(nsecs_t when,
-                                                  const InputReaderConfiguration* config,
-                                                  uint32_t changes) override;
+    [[nodiscard]] std::list<NotifyArgs> reconfigure(nsecs_t when,
+                                                    const InputReaderConfiguration& config,
+                                                    ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
     bool enableSensor(InputDeviceSensorType sensorType, std::chrono::microseconds samplingPeriod,
@@ -102,6 +108,9 @@
         }
     };
 
+    explicit SensorInputMapper(InputDeviceContext& deviceContext,
+                               const InputReaderConfiguration& readerConfig);
+
     static Axis createAxis(const AxisInfo& AxisInfo, const RawAbsoluteAxisInfo& rawAxisInfo);
 
     // Axes indexed by raw ABS_* axis index.
@@ -120,9 +129,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/SingleTouchInputMapper.cpp b/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp
index 13ad224..ed0e270 100644
--- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp
@@ -18,8 +18,9 @@
 
 namespace android {
 
-SingleTouchInputMapper::SingleTouchInputMapper(InputDeviceContext& deviceContext)
-      : TouchInputMapper(deviceContext) {}
+SingleTouchInputMapper::SingleTouchInputMapper(InputDeviceContext& deviceContext,
+                                               const InputReaderConfiguration& readerConfig)
+      : TouchInputMapper(deviceContext, readerConfig) {}
 
 SingleTouchInputMapper::~SingleTouchInputMapper() {}
 
@@ -41,7 +42,7 @@
         outState->rawPointerData.pointerCount = 1;
         outState->rawPointerData.idToIndex[0] = 0;
 
-        bool isHovering = mTouchButtonAccumulator.getToolType() != AMOTION_EVENT_TOOL_TYPE_MOUSE &&
+        bool isHovering = mTouchButtonAccumulator.getToolType() != ToolType::MOUSE &&
                 (mTouchButtonAccumulator.isHovering() ||
                  (mRawPointerAxes.pressure.valid &&
                   mSingleTouchMotionAccumulator.getAbsolutePressure() <= 0));
@@ -61,8 +62,8 @@
         outPointer.tiltX = mSingleTouchMotionAccumulator.getAbsoluteTiltX();
         outPointer.tiltY = mSingleTouchMotionAccumulator.getAbsoluteTiltY();
         outPointer.toolType = mTouchButtonAccumulator.getToolType();
-        if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
-            outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+        if (outPointer.toolType == ToolType::UNKNOWN) {
+            outPointer.toolType = ToolType::FINGER;
         }
         outPointer.isHovering = isHovering;
     }
diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h
index 662e6bc..dac53cf 100644
--- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h
@@ -23,7 +23,13 @@
 
 class SingleTouchInputMapper : public TouchInputMapper {
 public:
-    explicit SingleTouchInputMapper(InputDeviceContext& deviceContext);
+    template <class T, class... Args>
+    friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
+                                                const InputReaderConfiguration& readerConfig,
+                                                Args... args);
+    explicit SingleTouchInputMapper(InputDeviceContext& deviceContext,
+                                    const InputReaderConfiguration& readerConfig);
+
     ~SingleTouchInputMapper() override;
 
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
index c4564a4..05338da 100644
--- a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
@@ -20,8 +20,9 @@
 
 namespace android {
 
-SwitchInputMapper::SwitchInputMapper(InputDeviceContext& deviceContext)
-      : InputMapper(deviceContext), mSwitchValues(0), mUpdatedSwitchMask(0) {}
+SwitchInputMapper::SwitchInputMapper(InputDeviceContext& deviceContext,
+                                     const InputReaderConfiguration& readerConfig)
+      : InputMapper(deviceContext, readerConfig), mSwitchValues(0), mUpdatedSwitchMask(0) {}
 
 SwitchInputMapper::~SwitchInputMapper() {}
 
diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.h b/services/inputflinger/reader/mapper/SwitchInputMapper.h
index 06d6504..2fb48bb 100644
--- a/services/inputflinger/reader/mapper/SwitchInputMapper.h
+++ b/services/inputflinger/reader/mapper/SwitchInputMapper.h
@@ -22,7 +22,10 @@
 
 class SwitchInputMapper : public InputMapper {
 public:
-    explicit SwitchInputMapper(InputDeviceContext& deviceContext);
+    template <class T, class... Args>
+    friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
+                                                const InputReaderConfiguration& readerConfig,
+                                                Args... args);
     virtual ~SwitchInputMapper();
 
     virtual uint32_t getSources() const override;
@@ -35,6 +38,8 @@
     uint32_t mSwitchValues;
     uint32_t mUpdatedSwitchMask;
 
+    explicit SwitchInputMapper(InputDeviceContext& deviceContext,
+                               const InputReaderConfiguration& readerConfig);
     void processSwitch(int32_t switchCode, int32_t switchValue);
     [[nodiscard]] std::list<NotifyArgs> sync(nsecs_t when);
 };
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index b53fc73..f4d50b8 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -121,12 +121,11 @@
 
 // --- TouchInputMapper ---
 
-TouchInputMapper::TouchInputMapper(InputDeviceContext& deviceContext)
-      : InputMapper(deviceContext),
+TouchInputMapper::TouchInputMapper(InputDeviceContext& deviceContext,
+                                   const InputReaderConfiguration& readerConfig)
+      : InputMapper(deviceContext, readerConfig),
         mTouchButtonAccumulator(deviceContext),
-        mSource(0),
-        mDeviceMode(DeviceMode::DISABLED),
-        mInputDeviceOrientation(ui::ROTATION_0) {}
+        mConfig(readerConfig) {}
 
 TouchInputMapper::~TouchInputMapper() {}
 
@@ -134,16 +133,16 @@
     return mSource;
 }
 
-void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+void TouchInputMapper::populateDeviceInfo(InputDeviceInfo& info) {
     InputMapper::populateDeviceInfo(info);
 
     if (mDeviceMode == DeviceMode::DISABLED) {
         return;
     }
 
-    info->addMotionRange(mOrientedRanges.x);
-    info->addMotionRange(mOrientedRanges.y);
-    info->addMotionRange(mOrientedRanges.pressure);
+    info.addMotionRange(mOrientedRanges.x);
+    info.addMotionRange(mOrientedRanges.y);
+    info.addMotionRange(mOrientedRanges.pressure);
 
     if (mDeviceMode == DeviceMode::UNSCALED && mSource == AINPUT_SOURCE_TOUCHPAD) {
         // Populate RELATIVE_X and RELATIVE_Y motion ranges for touchpad capture mode.
@@ -153,46 +152,46 @@
         // touchpad in one sample cycle.
         const InputDeviceInfo::MotionRange& x = mOrientedRanges.x;
         const InputDeviceInfo::MotionRange& y = mOrientedRanges.y;
-        info->addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, mSource, -x.max, x.max, x.flat, x.fuzz,
-                             x.resolution);
-        info->addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, mSource, -y.max, y.max, y.flat, y.fuzz,
-                             y.resolution);
+        info.addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, mSource, -x.max, x.max, x.flat, x.fuzz,
+                            x.resolution);
+        info.addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, mSource, -y.max, y.max, y.flat, y.fuzz,
+                            y.resolution);
     }
 
     if (mOrientedRanges.size) {
-        info->addMotionRange(*mOrientedRanges.size);
+        info.addMotionRange(*mOrientedRanges.size);
     }
 
     if (mOrientedRanges.touchMajor) {
-        info->addMotionRange(*mOrientedRanges.touchMajor);
-        info->addMotionRange(*mOrientedRanges.touchMinor);
+        info.addMotionRange(*mOrientedRanges.touchMajor);
+        info.addMotionRange(*mOrientedRanges.touchMinor);
     }
 
     if (mOrientedRanges.toolMajor) {
-        info->addMotionRange(*mOrientedRanges.toolMajor);
-        info->addMotionRange(*mOrientedRanges.toolMinor);
+        info.addMotionRange(*mOrientedRanges.toolMajor);
+        info.addMotionRange(*mOrientedRanges.toolMinor);
     }
 
     if (mOrientedRanges.orientation) {
-        info->addMotionRange(*mOrientedRanges.orientation);
+        info.addMotionRange(*mOrientedRanges.orientation);
     }
 
     if (mOrientedRanges.distance) {
-        info->addMotionRange(*mOrientedRanges.distance);
+        info.addMotionRange(*mOrientedRanges.distance);
     }
 
     if (mOrientedRanges.tilt) {
-        info->addMotionRange(*mOrientedRanges.tilt);
+        info.addMotionRange(*mOrientedRanges.tilt);
     }
 
     if (mCursorScrollAccumulator.haveRelativeVWheel()) {
-        info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
+        info.addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
     }
     if (mCursorScrollAccumulator.haveRelativeHWheel()) {
-        info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
+        info.addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
     }
-    info->setButtonUnderPad(mParameters.hasButtonUnderPad);
-    info->setUsiVersion(mParameters.usiVersion);
+    info.setButtonUnderPad(mParameters.hasButtonUnderPad);
+    info.setUsiVersion(mParameters.usiVersion);
 }
 
 void TouchInputMapper::dump(std::string& dump) {
@@ -229,11 +228,12 @@
         dump += StringPrintf(INDENT4 "[%d]: id=%d, x=%d, y=%d, pressure=%d, "
                                      "touchMajor=%d, touchMinor=%d, toolMajor=%d, toolMinor=%d, "
                                      "orientation=%d, tiltX=%d, tiltY=%d, distance=%d, "
-                                     "toolType=%d, isHovering=%s\n",
+                                     "toolType=%s, isHovering=%s\n",
                              i, pointer.id, pointer.x, pointer.y, pointer.pressure,
                              pointer.touchMajor, pointer.touchMinor, pointer.toolMajor,
                              pointer.toolMinor, pointer.orientation, pointer.tiltX, pointer.tiltY,
-                             pointer.distance, pointer.toolType, toString(pointer.isHovering));
+                             pointer.distance, ftl::enum_string(pointer.toolType).c_str(),
+                             toString(pointer.isHovering));
     }
 
     dump += StringPrintf(INDENT3 "Last Cooked Button State: 0x%08x\n",
@@ -248,7 +248,7 @@
                                      "pressure=%0.3f, touchMajor=%0.3f, touchMinor=%0.3f, "
                                      "toolMajor=%0.3f, toolMinor=%0.3f, "
                                      "orientation=%0.3f, tilt=%0.3f, distance=%0.3f, "
-                                     "toolType=%d, isHovering=%s\n",
+                                     "toolType=%s, isHovering=%s\n",
                              i, pointerProperties.id, pointerCoords.getX(), pointerCoords.getY(),
                              pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X),
                              pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y),
@@ -260,7 +260,7 @@
                              pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION),
                              pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TILT),
                              pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_DISTANCE),
-                             pointerProperties.toolType,
+                             ftl::enum_string(pointerProperties.toolType).c_str(),
                              toString(mLastCookedState.cookedPointerData.isHovering(i)));
     }
 
@@ -286,19 +286,19 @@
     }
 }
 
-std::list<NotifyArgs> TouchInputMapper::configure(nsecs_t when,
-                                                  const InputReaderConfiguration* config,
-                                                  uint32_t changes) {
-    std::list<NotifyArgs> out = InputMapper::configure(when, config, changes);
+std::list<NotifyArgs> TouchInputMapper::reconfigure(nsecs_t when,
+                                                    const InputReaderConfiguration& config,
+                                                    ConfigurationChanges changes) {
+    std::list<NotifyArgs> out = InputMapper::reconfigure(when, config, changes);
 
-    mConfig = *config;
+    mConfig = config;
 
     // Full configuration should happen the first time configure is called and
     // when the device type is changed. Changing a device type can affect
     // various other parameters so should result in a reconfiguration.
-    if (!changes || (changes & InputReaderConfiguration::CHANGE_DEVICE_TYPE)) {
+    if (!changes.any() || changes.test(InputReaderConfiguration::Change::DEVICE_TYPE)) {
         // Configure basic parameters.
-        configureParameters();
+        mParameters = computeParameters(getDeviceContext());
 
         // Configure common accumulators.
         mCursorScrollAccumulator.configure(getDeviceContext());
@@ -312,33 +312,34 @@
         resolveCalibration();
     }
 
-    if (!changes || (changes & InputReaderConfiguration::CHANGE_TOUCH_AFFINE_TRANSFORMATION)) {
+    if (!changes.any() ||
+        changes.test(InputReaderConfiguration::Change::TOUCH_AFFINE_TRANSFORMATION)) {
         // Update location calibration to reflect current settings
         updateAffineTransformation();
     }
 
-    if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED)) {
+    if (!changes.any() || changes.test(InputReaderConfiguration::Change::POINTER_SPEED)) {
         // Update pointer speed.
         mPointerVelocityControl.setParameters(mConfig.pointerVelocityControlParameters);
         mWheelXVelocityControl.setParameters(mConfig.wheelVelocityControlParameters);
         mWheelYVelocityControl.setParameters(mConfig.wheelVelocityControlParameters);
     }
 
+    using namespace ftl::flag_operators;
     bool resetNeeded = false;
-    if (!changes ||
-        (changes &
-         (InputReaderConfiguration::CHANGE_DISPLAY_INFO |
-          InputReaderConfiguration::CHANGE_POINTER_CAPTURE |
-          InputReaderConfiguration::CHANGE_POINTER_GESTURE_ENABLEMENT |
-          InputReaderConfiguration::CHANGE_SHOW_TOUCHES |
-          InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE |
-          InputReaderConfiguration::CHANGE_DEVICE_TYPE))) {
+    if (!changes.any() ||
+        changes.any(InputReaderConfiguration::Change::DISPLAY_INFO |
+                    InputReaderConfiguration::Change::POINTER_CAPTURE |
+                    InputReaderConfiguration::Change::POINTER_GESTURE_ENABLEMENT |
+                    InputReaderConfiguration::Change::SHOW_TOUCHES |
+                    InputReaderConfiguration::Change::EXTERNAL_STYLUS_PRESENCE |
+                    InputReaderConfiguration::Change::DEVICE_TYPE)) {
         // Configure device sources, display dimensions, orientation and
         // scaling factors.
         configureInputDevice(when, &resetNeeded);
     }
 
-    if (changes && resetNeeded) {
+    if (changes.any() && resetNeeded) {
         out += reset(when);
 
         // Send reset, unless this is the first time the device has been configured,
@@ -358,116 +359,119 @@
     }
 }
 
-void TouchInputMapper::configureParameters() {
+TouchInputMapper::Parameters TouchInputMapper::computeParameters(
+        const InputDeviceContext& deviceContext) {
+    Parameters parameters;
     // Use the pointer presentation mode for devices that do not support distinct
     // multitouch.  The spot-based presentation relies on being able to accurately
     // locate two or more fingers on the touch pad.
-    mParameters.gestureMode = getDeviceContext().hasInputProperty(INPUT_PROP_SEMI_MT)
+    parameters.gestureMode = deviceContext.hasInputProperty(INPUT_PROP_SEMI_MT)
             ? Parameters::GestureMode::SINGLE_TOUCH
             : Parameters::GestureMode::MULTI_TOUCH;
 
-    std::string gestureModeString;
-    if (getDeviceContext().getConfiguration().tryGetProperty("touch.gestureMode",
-                                                             gestureModeString)) {
-        if (gestureModeString == "single-touch") {
-            mParameters.gestureMode = Parameters::GestureMode::SINGLE_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());
+    const PropertyMap& config = deviceContext.getConfiguration();
+    std::optional<std::string> gestureModeString = config.getString("touch.gestureMode");
+    if (gestureModeString.has_value()) {
+        if (*gestureModeString == "single-touch") {
+            parameters.gestureMode = Parameters::GestureMode::SINGLE_TOUCH;
+        } else if (*gestureModeString == "multi-touch") {
+            parameters.gestureMode = Parameters::GestureMode::MULTI_TOUCH;
+        } else if (*gestureModeString != "default") {
+            ALOGW("Invalid value for touch.gestureMode: '%s'", gestureModeString->c_str());
         }
     }
 
-    configureDeviceType();
+    parameters.deviceType = computeDeviceType(deviceContext);
 
-    mParameters.hasButtonUnderPad = getDeviceContext().hasInputProperty(INPUT_PROP_BUTTONPAD);
+    parameters.hasButtonUnderPad = deviceContext.hasInputProperty(INPUT_PROP_BUTTONPAD);
 
-    mParameters.orientationAware = mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN;
-    getDeviceContext().getConfiguration().tryGetProperty("touch.orientationAware",
-                                                         mParameters.orientationAware);
+    parameters.orientationAware =
+            config.getBool("touch.orientationAware")
+                    .value_or(parameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN);
 
-    mParameters.orientation = ui::ROTATION_0;
-    std::string orientationString;
-    if (getDeviceContext().getConfiguration().tryGetProperty("touch.orientation",
-                                                             orientationString)) {
-        if (mParameters.deviceType != Parameters::DeviceType::TOUCH_SCREEN) {
+    parameters.orientation = ui::ROTATION_0;
+    std::optional<std::string> orientationString = config.getString("touch.orientation");
+    if (orientationString.has_value()) {
+        if (parameters.deviceType != Parameters::DeviceType::TOUCH_SCREEN) {
             ALOGW("The configuration 'touch.orientation' is only supported for touchscreens.");
-        } else if (orientationString == "ORIENTATION_90") {
-            mParameters.orientation = ui::ROTATION_90;
-        } else if (orientationString == "ORIENTATION_180") {
-            mParameters.orientation = ui::ROTATION_180;
-        } 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_90") {
+            parameters.orientation = ui::ROTATION_90;
+        } else if (*orientationString == "ORIENTATION_180") {
+            parameters.orientation = ui::ROTATION_180;
+        } else if (*orientationString == "ORIENTATION_270") {
+            parameters.orientation = ui::ROTATION_270;
+        } else if (*orientationString != "ORIENTATION_0") {
+            ALOGW("Invalid value for touch.orientation: '%s'", orientationString->c_str());
         }
     }
 
-    mParameters.hasAssociatedDisplay = false;
-    mParameters.associatedDisplayIsExternal = false;
-    if (mParameters.orientationAware ||
-        mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN ||
-        mParameters.deviceType == Parameters::DeviceType::POINTER ||
-        (mParameters.deviceType == Parameters::DeviceType::TOUCH_NAVIGATION &&
-         getDeviceContext().getAssociatedViewport())) {
-        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();
+    parameters.hasAssociatedDisplay = false;
+    parameters.associatedDisplayIsExternal = false;
+    if (parameters.orientationAware ||
+        parameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN ||
+        parameters.deviceType == Parameters::DeviceType::POINTER ||
+        (parameters.deviceType == Parameters::DeviceType::TOUCH_NAVIGATION &&
+         deviceContext.getAssociatedViewport())) {
+        parameters.hasAssociatedDisplay = true;
+        if (parameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN) {
+            parameters.associatedDisplayIsExternal = deviceContext.isExternal();
+            parameters.uniqueDisplayId = config.getString("touch.displayId").value_or("").c_str();
         }
     }
-    if (getDeviceContext().getAssociatedDisplayPort()) {
-        mParameters.hasAssociatedDisplay = true;
+    if (deviceContext.getAssociatedDisplayPort()) {
+        parameters.hasAssociatedDisplay = true;
     }
 
     // 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);
+    parameters.wake = config.getBool("touch.wake").value_or(deviceContext.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()) {
+        parameters.usiVersion = {
+                .majorVersion = *usiVersionMajor,
+                .minorVersion = *usiVersionMinor,
+        };
     }
 
-    mParameters.enableForInactiveViewport = false;
-    getDeviceContext().getConfiguration().tryGetProperty("touch.enableForInactiveViewport",
-                                                         mParameters.enableForInactiveViewport);
+    parameters.enableForInactiveViewport =
+            config.getBool("touch.enableForInactiveViewport").value_or(false);
+
+    return parameters;
 }
 
-void TouchInputMapper::configureDeviceType() {
-    if (getDeviceContext().hasInputProperty(INPUT_PROP_DIRECT)) {
+TouchInputMapper::Parameters::DeviceType TouchInputMapper::computeDeviceType(
+        const InputDeviceContext& deviceContext) {
+    Parameters::DeviceType deviceType;
+    if (deviceContext.hasInputProperty(INPUT_PROP_DIRECT)) {
         // The device is a touch screen.
-        mParameters.deviceType = Parameters::DeviceType::TOUCH_SCREEN;
-    } else if (getDeviceContext().hasInputProperty(INPUT_PROP_POINTER)) {
+        deviceType = Parameters::DeviceType::TOUCH_SCREEN;
+    } else if (deviceContext.hasInputProperty(INPUT_PROP_POINTER)) {
         // The device is a pointing device like a track pad.
-        mParameters.deviceType = Parameters::DeviceType::POINTER;
+        deviceType = Parameters::DeviceType::POINTER;
     } else {
         // The device is a touch pad of unknown purpose.
-        mParameters.deviceType = Parameters::DeviceType::POINTER;
+        deviceType = Parameters::DeviceType::POINTER;
     }
 
     // Type association takes precedence over the device type found in the idc file.
-    std::string deviceTypeString = getDeviceContext().getDeviceTypeAssociation().value_or("");
+    std::string deviceTypeString = deviceContext.getDeviceTypeAssociation().value_or("");
     if (deviceTypeString.empty()) {
-        getDeviceContext().getConfiguration().tryGetProperty("touch.deviceType", deviceTypeString);
+        deviceTypeString =
+                deviceContext.getConfiguration().getString("touch.deviceType").value_or("");
     }
     if (deviceTypeString == "touchScreen") {
-        mParameters.deviceType = Parameters::DeviceType::TOUCH_SCREEN;
+        deviceType = Parameters::DeviceType::TOUCH_SCREEN;
     } else if (deviceTypeString == "touchNavigation") {
-        mParameters.deviceType = Parameters::DeviceType::TOUCH_NAVIGATION;
+        deviceType = Parameters::DeviceType::TOUCH_NAVIGATION;
     } else if (deviceTypeString == "pointer") {
-        mParameters.deviceType = Parameters::DeviceType::POINTER;
+        deviceType = Parameters::DeviceType::POINTER;
     } else if (deviceTypeString != "default" && deviceTypeString != "") {
         ALOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.c_str());
     }
+    return deviceType;
 }
 
 void TouchInputMapper::dumpParameters(std::string& dump) {
@@ -805,40 +809,39 @@
         };
     }
 
-    // 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() {
@@ -877,14 +880,26 @@
             : ui::Transform();
 
     // 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.
+    // - In DIRECT mode, 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.
+    // - In POINTER mode, we cannot assume that the display and the touch device have the same
+    //   aspect ratio, since it is likely to be untrue for devices like external drawing tablets.
+    //   In this case, we used a fixed scale so that 1) we use the same scale across both the x and
+    //   y axes to ensure the mapping does not stretch gestures, and 2) the entire region of the
+    //   display can be reached by the touch device.
     // - 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);
+    if (mDeviceMode == DeviceMode::DIRECT) {
+        scaleRawToDisplay.set(xScale, 0, 0, yScale);
+    } else if (mDeviceMode == DeviceMode::POINTER) {
+        const float fixedScale = std::max(xScale, yScale);
+        scaleRawToDisplay.set(fixedScale, 0, 0, fixedScale);
+    } else {
+        LOG_ALWAYS_FATAL("computeInputTransform can only be used for DIRECT and POINTER modes");
+    }
 
     // Step 5: Undo the display rotation to bring us back to the un-rotated display coordinate space
     // that InputReader uses.
@@ -1161,92 +1176,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() {
@@ -1600,10 +1602,10 @@
                     mCurrentRawState.rawPointerData.pointerForId(id);
             if (isStylusToolType(pointer.toolType)) {
                 mCurrentCookedState.stylusIdBits.markBit(id);
-            } else if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_FINGER ||
-                       pointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+            } else if (pointer.toolType == ToolType::FINGER ||
+                       pointer.toolType == ToolType::UNKNOWN) {
                 mCurrentCookedState.fingerIdBits.markBit(id);
-            } else if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_MOUSE) {
+            } else if (pointer.toolType == ToolType::MOUSE) {
                 mCurrentCookedState.mouseIdBits.markBit(id);
             }
         }
@@ -1676,7 +1678,6 @@
     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 |
@@ -1723,7 +1724,7 @@
     PointerCoords& coords = currentPointerData.editPointerCoordsWithId(*mFusedStylusPointerId);
     coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure);
 
-    if (mExternalStylusState.toolType != AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+    if (mExternalStylusState.toolType != ToolType::UNKNOWN) {
         PointerProperties& properties =
                 currentPointerData.editPointerPropertiesWithId(*mFusedStylusPointerId);
         properties.toolType = mExternalStylusState.toolType;
@@ -1861,6 +1862,27 @@
         }
     }
 
+    if (!mCurrentRawState.rawPointerData.hoveringIdBits.isEmpty() &&
+        mCurrentRawState.rawPointerData.touchingIdBits.isEmpty() &&
+        mDeviceMode != DeviceMode::UNSCALED) {
+        // We have hovering pointers, and there are no touching pointers.
+        bool hoveringPointersInFrame = false;
+        auto hoveringIds = mCurrentRawState.rawPointerData.hoveringIdBits;
+        while (!hoveringIds.isEmpty()) {
+            uint32_t id = hoveringIds.clearFirstMarkedBit();
+            const auto& pointer = mCurrentRawState.rawPointerData.pointerForId(id);
+            if (isPointInsidePhysicalFrame(pointer.x, pointer.y)) {
+                hoveringPointersInFrame = true;
+                break;
+            }
+        }
+        if (!hoveringPointersInFrame) {
+            // All hovering pointers are outside the physical frame.
+            outConsumed = true;
+            return out;
+        }
+    }
+
     if (mLastRawState.rawPointerData.touchingIdBits.isEmpty() &&
         !mCurrentRawState.rawPointerData.touchingIdBits.isEmpty()) {
         // Pointer just went down.  Check for virtual key press or off-screen touches.
@@ -1871,7 +1893,7 @@
         if (!isPointInsidePhysicalFrame(pointer.x, pointer.y) &&
             mDeviceMode != DeviceMode::UNSCALED) {
             // If exactly one pointer went down, check for virtual key hit.
-            // Otherwise we will drop the entire stroke.
+            // Otherwise, we will drop the entire stroke.
             if (mCurrentRawState.rawPointerData.touchingIdBits.count() == 1) {
                 const VirtualKey* virtualKey = findVirtualKeyHit(pointer.x, pointer.y);
                 if (virtualKey) {
@@ -2692,13 +2714,12 @@
         // 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();
         pointerProperties.id = 0;
-        pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+        pointerProperties.toolType = ToolType::FINGER;
 
         PointerCoords pointerCoords;
         pointerCoords.clear();
@@ -2899,8 +2920,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();
@@ -2908,7 +2928,7 @@
         mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
         mPointerGesture.currentGestureProperties[0].clear();
         mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
-        mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+        mPointerGesture.currentGestureProperties[0].toolType = ToolType::FINGER;
         mPointerGesture.currentGestureCoords[0].clear();
         mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
         mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
@@ -2926,8 +2946,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");
@@ -2944,8 +2963,7 @@
                     mPointerGesture.currentGestureProperties[0].clear();
                     mPointerGesture.currentGestureProperties[0].id =
                             mPointerGesture.activeGestureId;
-                    mPointerGesture.currentGestureProperties[0].toolType =
-                            AMOTION_EVENT_TOOL_TYPE_FINGER;
+                    mPointerGesture.currentGestureProperties[0].toolType = ToolType::FINGER;
                     mPointerGesture.currentGestureCoords[0].clear();
                     mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
                                                                          mPointerGesture.tapX);
@@ -2989,8 +3007,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;
@@ -3026,15 +3043,14 @@
             down = false;
         }
 
-        float x, y;
-        mPointerController->getPosition(&x, &y);
+        const auto [x, y] = mPointerController->getPosition();
 
         mPointerGesture.currentGestureIdBits.clear();
         mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
         mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
         mPointerGesture.currentGestureProperties[0].clear();
         mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
-        mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+        mPointerGesture.currentGestureProperties[0].toolType = ToolType::FINGER;
         mPointerGesture.currentGestureCoords[0].clear();
         mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
         mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
@@ -3052,8 +3068,6 @@
         prepareMultiFingerPointerGestures(when, outCancelPreviousGesture, outFinishPreviousGesture);
     }
 
-    mPointerController->setButtonState(mCurrentRawState.buttonState);
-
     if (DEBUG_GESTURES) {
         ALOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, "
               "currentGestureMode=%d, currentGestureIdBits=0x%08x, "
@@ -3066,9 +3080,10 @@
             uint32_t index = mPointerGesture.currentGestureIdToIndex[id];
             const PointerProperties& properties = mPointerGesture.currentGestureProperties[index];
             const PointerCoords& coords = mPointerGesture.currentGestureCoords[index];
-            ALOGD("  currentGesture[%d]: index=%d, toolType=%d, "
+            ALOGD("  currentGesture[%d]: index=%d, toolType=%s, "
                   "x=%0.3f, y=%0.3f, pressure=%0.3f",
-                  id, index, properties.toolType, coords.getAxisValue(AMOTION_EVENT_AXIS_X),
+                  id, index, ftl::enum_string(properties.toolType).c_str(),
+                  coords.getAxisValue(AMOTION_EVENT_AXIS_X),
                   coords.getAxisValue(AMOTION_EVENT_AXIS_Y),
                   coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
         }
@@ -3077,9 +3092,10 @@
             uint32_t index = mPointerGesture.lastGestureIdToIndex[id];
             const PointerProperties& properties = mPointerGesture.lastGestureProperties[index];
             const PointerCoords& coords = mPointerGesture.lastGestureCoords[index];
-            ALOGD("  lastGesture[%d]: index=%d, toolType=%d, "
+            ALOGD("  lastGesture[%d]: index=%d, toolType=%s, "
                   "x=%0.3f, y=%0.3f, pressure=%0.3f",
-                  id, index, properties.toolType, coords.getAxisValue(AMOTION_EVENT_AXIS_X),
+                  id, index, ftl::enum_string(properties.toolType).c_str(),
+                  coords.getAxisValue(AMOTION_EVENT_AXIS_X),
                   coords.getAxisValue(AMOTION_EVENT_AXIS_Y),
                   coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
         }
@@ -3194,8 +3210,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.
@@ -3368,7 +3384,7 @@
         mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
         mPointerGesture.currentGestureProperties[0].clear();
         mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
-        mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+        mPointerGesture.currentGestureProperties[0].toolType = ToolType::FINGER;
         mPointerGesture.currentGestureCoords[0].clear();
         mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
                                                              mPointerGesture.referenceGestureX);
@@ -3461,7 +3477,7 @@
 
             mPointerGesture.currentGestureProperties[i].clear();
             mPointerGesture.currentGestureProperties[i].id = gestureId;
-            mPointerGesture.currentGestureProperties[i].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+            mPointerGesture.currentGestureProperties[i].toolType = ToolType::FINGER;
             mPointerGesture.currentGestureCoords[i].clear();
             mPointerGesture.currentGestureCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X,
                                                                  mPointerGesture.referenceGestureX +
@@ -3503,15 +3519,19 @@
     if (!mCurrentCookedState.stylusIdBits.isEmpty()) {
         uint32_t id = mCurrentCookedState.stylusIdBits.firstMarkedBit();
         uint32_t index = mCurrentCookedState.cookedPointerData.idToIndex[id];
-        mPointerController
-                ->setPosition(mCurrentCookedState.cookedPointerData.pointerCoords[index].getX(),
-                              mCurrentCookedState.cookedPointerData.pointerCoords[index].getY());
-
         hovering = mCurrentCookedState.cookedPointerData.hoveringIdBits.hasBit(id);
         down = !hovering;
 
-        float x, y;
-        mPointerController->getPosition(&x, &y);
+        float x = mCurrentCookedState.cookedPointerData.pointerCoords[index].getX();
+        float y = mCurrentCookedState.cookedPointerData.pointerCoords[index].getY();
+        // Styluses are configured specifically for one display. We only update the
+        // PointerController for this stylus if the PointerController is configured for
+        // the same display as this stylus,
+        if (getAssociatedDisplayId() == mViewport.displayId) {
+            mPointerController->setPosition(x, y);
+            std::tie(x, y) = mPointerController->getPosition();
+        }
+
         mPointerSimple.currentCoords.copyFrom(
                 mCurrentCookedState.cookedPointerData.pointerCoords[index]);
         mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
@@ -3524,7 +3544,7 @@
         hovering = false;
     }
 
-    return dispatchPointerSimple(when, readTime, policyFlags, down, hovering);
+    return dispatchPointerSimple(when, readTime, policyFlags, down, hovering, mViewport.displayId);
 }
 
 std::list<NotifyArgs> TouchInputMapper::abortPointerStylus(nsecs_t when, nsecs_t readTime,
@@ -3549,9 +3569,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);
@@ -3568,7 +3587,8 @@
         hovering = false;
     }
 
-    return dispatchPointerSimple(when, readTime, policyFlags, down, hovering);
+    const int32_t displayId = mPointerController->getDisplayId();
+    return dispatchPointerSimple(when, readTime, policyFlags, down, hovering, displayId);
 }
 
 std::list<NotifyArgs> TouchInputMapper::abortPointerMouse(nsecs_t when, nsecs_t readTime,
@@ -3582,24 +3602,23 @@
 
 std::list<NotifyArgs> TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime,
                                                               uint32_t policyFlags, bool down,
-                                                              bool hovering) {
+                                                              bool hovering, int32_t displayId) {
     LOG_ALWAYS_FATAL_IF(mDeviceMode != DeviceMode::POINTER,
                         "%s cannot be used when the device is not in POINTER mode.", __func__);
     std::list<NotifyArgs> out;
     int32_t metaState = getContext()->getGlobalMetaState();
+    auto cursorPosition = mPointerSimple.currentCoords.getXYValue();
 
-    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);
+    if (displayId == mPointerController->getDisplayId()) {
+        std::tie(cursorPosition.x, cursorPosition.y) = mPointerController->getPosition();
+        if (down || hovering) {
+            mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
+            mPointerController->clearSpots();
+            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);
 
     if (mPointerSimple.down && !down) {
         mPointerSimple.down = false;
@@ -3610,8 +3629,9 @@
                                        0, metaState, mLastRawState.buttonState,
                                        MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
                                        &mPointerSimple.lastProperties, &mPointerSimple.lastCoords,
-                                       mOrientedXPrecision, mOrientedYPrecision, xCursorPosition,
-                                       yCursorPosition, mPointerSimple.downTime,
+                                       mOrientedXPrecision, mOrientedYPrecision,
+                                       mPointerSimple.lastCursorX, mPointerSimple.lastCursorY,
+                                       mPointerSimple.downTime,
                                        /* videoFrames */ {}));
     }
 
@@ -3619,15 +3639,15 @@
         mPointerSimple.hovering = false;
 
         // Send hover exit.
-        out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(),
-                                       mSource, displayId, policyFlags,
-                                       AMOTION_EVENT_ACTION_HOVER_EXIT, 0, 0, metaState,
-                                       mLastRawState.buttonState, MotionClassification::NONE,
-                                       AMOTION_EVENT_EDGE_FLAG_NONE, 1,
-                                       &mPointerSimple.lastProperties, &mPointerSimple.lastCoords,
-                                       mOrientedXPrecision, mOrientedYPrecision, xCursorPosition,
-                                       yCursorPosition, mPointerSimple.downTime,
-                                       /* videoFrames */ {}));
+        out.push_back(
+                NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
+                                 displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_EXIT, 0, 0,
+                                 metaState, mLastRawState.buttonState, MotionClassification::NONE,
+                                 AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.lastProperties,
+                                 &mPointerSimple.lastCoords, mOrientedXPrecision,
+                                 mOrientedYPrecision, mPointerSimple.lastCursorX,
+                                 mPointerSimple.lastCursorY, mPointerSimple.downTime,
+                                 /* videoFrames */ {}));
     }
 
     if (down) {
@@ -3643,7 +3663,7 @@
                                            AMOTION_EVENT_EDGE_FLAG_NONE, 1,
                                            &mPointerSimple.currentProperties,
                                            &mPointerSimple.currentCoords, mOrientedXPrecision,
-                                           mOrientedYPrecision, xCursorPosition, yCursorPosition,
+                                           mOrientedYPrecision, cursorPosition.x, cursorPosition.y,
                                            mPointerSimple.downTime, /* videoFrames */ {}));
         }
 
@@ -3654,7 +3674,7 @@
                                        MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
                                        &mPointerSimple.currentProperties,
                                        &mPointerSimple.currentCoords, mOrientedXPrecision,
-                                       mOrientedYPrecision, xCursorPosition, yCursorPosition,
+                                       mOrientedYPrecision, cursorPosition.x, cursorPosition.y,
                                        mPointerSimple.downTime, /* videoFrames */ {}));
     }
 
@@ -3670,7 +3690,7 @@
                                            AMOTION_EVENT_EDGE_FLAG_NONE, 1,
                                            &mPointerSimple.currentProperties,
                                            &mPointerSimple.currentCoords, mOrientedXPrecision,
-                                           mOrientedYPrecision, xCursorPosition, yCursorPosition,
+                                           mOrientedYPrecision, cursorPosition.x, cursorPosition.y,
                                            mPointerSimple.downTime, /* videoFrames */ {}));
         }
 
@@ -3681,8 +3701,8 @@
                                  metaState, mCurrentRawState.buttonState,
                                  MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
                                  &mPointerSimple.currentProperties, &mPointerSimple.currentCoords,
-                                 mOrientedXPrecision, mOrientedYPrecision, xCursorPosition,
-                                 yCursorPosition, mPointerSimple.downTime, /* videoFrames */ {}));
+                                 mOrientedXPrecision, mOrientedYPrecision, cursorPosition.x,
+                                 cursorPosition.y, mPointerSimple.downTime, /* videoFrames */ {}));
     }
 
     if (mCurrentRawState.rawVScroll || mCurrentRawState.rawHScroll) {
@@ -3702,8 +3722,8 @@
                                        0, 0, metaState, mCurrentRawState.buttonState,
                                        MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
                                        &mPointerSimple.currentProperties, &pointerCoords,
-                                       mOrientedXPrecision, mOrientedYPrecision, xCursorPosition,
-                                       yCursorPosition, mPointerSimple.downTime,
+                                       mOrientedXPrecision, mOrientedYPrecision, cursorPosition.x,
+                                       cursorPosition.y, mPointerSimple.downTime,
                                        /* videoFrames */ {}));
     }
 
@@ -3713,8 +3733,8 @@
         mPointerSimple.lastProperties.copyFrom(mPointerSimple.currentProperties);
         mPointerSimple.displayId = displayId;
         mPointerSimple.source = mSource;
-        mPointerSimple.lastCursorX = xCursorPosition;
-        mPointerSimple.lastCursorY = yCursorPosition;
+        mPointerSimple.lastCursorX = cursorPosition.x;
+        mPointerSimple.lastCursorY = cursorPosition.y;
     } else {
         mPointerSimple.reset();
     }
@@ -3820,7 +3840,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..d8b59ca 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>
 
@@ -75,8 +78,8 @@
         int32_t distance{};
         int32_t tiltX{};
         int32_t tiltY{};
-        // A fully decoded AMOTION_EVENT_TOOL_TYPE constant.
-        int32_t toolType{AMOTION_EVENT_TOOL_TYPE_UNKNOWN};
+        // A fully decoded ToolType constant.
+        ToolType toolType{ToolType::UNKNOWN};
         bool isHovering{false};
     };
 
@@ -143,15 +146,14 @@
 
 class TouchInputMapper : public InputMapper {
 public:
-    explicit TouchInputMapper(InputDeviceContext& deviceContext);
     ~TouchInputMapper() override;
 
     uint32_t getSources() const override;
-    void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
+    void populateDeviceInfo(InputDeviceInfo& deviceInfo) override;
     void dump(std::string& dump) override;
-    [[nodiscard]] std::list<NotifyArgs> configure(nsecs_t when,
-                                                  const InputReaderConfiguration* config,
-                                                  uint32_t changes) override;
+    [[nodiscard]] std::list<NotifyArgs> reconfigure(nsecs_t when,
+                                                    const InputReaderConfiguration& config,
+                                                    ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
 
@@ -188,7 +190,7 @@
     };
 
     // Input sources and device mode.
-    uint32_t mSource;
+    uint32_t mSource{0};
 
     enum class DeviceMode {
         DISABLED,   // input is disabled
@@ -199,7 +201,7 @@
 
         ftl_last = POINTER
     };
-    DeviceMode mDeviceMode;
+    DeviceMode mDeviceMode{DeviceMode::DISABLED};
 
     // The reader's configuration.
     InputReaderConfiguration mConfig;
@@ -354,26 +356,28 @@
     nsecs_t mExternalStylusFusionTimeout;
     bool mExternalStylusDataPending;
     // A subset of the buttons in mCurrentRawState that came from an external stylus.
-    int32_t mExternalStylusButtonsApplied;
+    int32_t mExternalStylusButtonsApplied{0};
 
     // True if we sent a HOVER_ENTER event.
-    bool mSentHoverEnter;
+    bool mSentHoverEnter{false};
 
     // Have we assigned pointer IDs for this stream
-    bool mHavePointerIds;
+    bool mHavePointerIds{false};
 
     // Is the current stream of direct touch events aborted
-    bool mCurrentMotionAborted;
+    bool mCurrentMotionAborted{false};
 
     // The time the primary pointer last went down.
-    nsecs_t mDownTime;
+    nsecs_t mDownTime{0};
 
     // The pointer controller, or null if the device is not a pointer.
     std::shared_ptr<PointerControllerInterface> mPointerController;
 
     std::vector<VirtualKey> mVirtualKeys;
 
-    virtual void configureParameters();
+    explicit TouchInputMapper(InputDeviceContext& deviceContext,
+                              const InputReaderConfiguration& readerConfig);
+
     virtual void dumpParameters(std::string& dump);
     virtual void configureRawPointerAxes();
     virtual void dumpRawPointerAxes(std::string& dump);
@@ -409,7 +413,7 @@
     // The orientation of the input device relative to that of the display panel. It specifies
     // the rotation of the input device coordinates required to produce the display panel
     // orientation, so it will depend on whether the device is orientation aware.
-    ui::Rotation mInputDeviceOrientation;
+    ui::Rotation mInputDeviceOrientation{ui::ROTATION_0};
 
     // The transform that maps the input device's raw coordinate space to the un-rotated display's
     // coordinate space. InputReader generates events in the un-rotated display's coordinate space.
@@ -510,7 +514,7 @@
         STYLUS,
         MOUSE,
     };
-    PointerUsage mPointerUsage;
+    PointerUsage mPointerUsage{PointerUsage::NONE};
 
     struct PointerGesture {
         enum class Mode {
@@ -786,7 +790,7 @@
 
     [[nodiscard]] std::list<NotifyArgs> dispatchPointerSimple(nsecs_t when, nsecs_t readTime,
                                                               uint32_t policyFlags, bool down,
-                                                              bool hovering);
+                                                              bool hovering, int32_t displayId);
     [[nodiscard]] std::list<NotifyArgs> abortPointerSimple(nsecs_t when, nsecs_t readTime,
                                                            uint32_t policyFlags);
 
@@ -818,9 +822,10 @@
 
     static void assignPointerIds(const RawState& last, RawState& current);
 
+    // Compute input transforms for DIRECT and POINTER modes.
     void computeInputTransforms();
-
-    void configureDeviceType();
+    static Parameters::DeviceType computeDeviceType(const InputDeviceContext& deviceContext);
+    static Parameters computeParameters(const InputDeviceContext& deviceContext);
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index d3af402..c72425a 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -16,10 +16,13 @@
 
 #include "../Macros.h"
 
+#include <chrono>
 #include <limits>
 #include <optional>
 
+#include <android-base/stringprintf.h>
 #include <android/input.h>
+#include <ftl/enum.h>
 #include <input/PrintTools.h>
 #include <linux/input-event-codes.h>
 #include <log/log_main.h>
@@ -56,7 +59,7 @@
         {std::numeric_limits<double>::infinity(), 15.04, -857.758},
 };
 
-const std::vector<double> sensitivityFactors = {1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 18, 20};
+const std::vector<double> sensitivityFactors = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 18};
 
 std::vector<double> createAccelerationCurveForSensitivity(int32_t sensitivity,
                                                           size_t propertySize) {
@@ -168,12 +171,23 @@
 
 } // namespace
 
-TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext)
-      : InputMapper(deviceContext),
+TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext,
+                                         const InputReaderConfiguration& readerConfig)
+      : InputMapper(deviceContext, readerConfig),
         mGestureInterpreter(NewGestureInterpreter(), DeleteGestureInterpreter),
         mPointerController(getContext()->getPointerController(getDeviceId())),
-        mStateConverter(deviceContext),
-        mGestureConverter(*getContext(), deviceContext, getDeviceId()) {
+        mStateConverter(deviceContext, mMotionAccumulator),
+        mGestureConverter(*getContext(), deviceContext, getDeviceId()),
+        mCapturedEventConverter(*getContext(), deviceContext, mMotionAccumulator, getDeviceId()) {
+    RawAbsoluteAxisInfo slotAxisInfo;
+    deviceContext.getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo);
+    if (!slotAxisInfo.valid || slotAxisInfo.maxValue <= 0) {
+        ALOGW("Touchpad \"%s\" doesn't have a valid ABS_MT_SLOT axis, and probably won't work "
+              "properly.",
+              deviceContext.getName().c_str());
+    }
+    mMotionAccumulator.configure(deviceContext, slotAxisInfo.maxValue + 1, true);
+
     mGestureInterpreter->Initialize(GESTURES_DEVCLASS_TOUCHPAD);
     mGestureInterpreter->SetHardwareProperties(createHardwareProperties(deviceContext));
     // Even though we don't explicitly delete copy/move semantics, it's safe to
@@ -205,51 +219,116 @@
     return AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD;
 }
 
+void TouchpadInputMapper::populateDeviceInfo(InputDeviceInfo& info) {
+    InputMapper::populateDeviceInfo(info);
+    if (mPointerCaptured) {
+        mCapturedEventConverter.populateMotionRanges(info);
+    } else {
+        mGestureConverter.populateMotionRanges(info);
+    }
+}
+
 void TouchpadInputMapper::dump(std::string& dump) {
     dump += INDENT2 "Touchpad Input Mapper:\n";
+    if (mProcessing) {
+        dump += INDENT3 "Currently processing a hardware state\n";
+    }
+    if (mResettingInterpreter) {
+        dump += INDENT3 "Currently resetting gesture interpreter\n";
+    }
+    dump += StringPrintf(INDENT3 "Pointer captured: %s\n", toString(mPointerCaptured));
     dump += INDENT3 "Gesture converter:\n";
     dump += addLinePrefix(mGestureConverter.dump(), INDENT4);
     dump += INDENT3 "Gesture properties:\n";
     dump += addLinePrefix(mPropertyProvider.dump(), INDENT4);
+    dump += INDENT3 "Captured event converter:\n";
+    dump += addLinePrefix(mCapturedEventConverter.dump(), INDENT4);
 }
 
-std::list<NotifyArgs> TouchpadInputMapper::configure(nsecs_t when,
-                                                     const InputReaderConfiguration* config,
-                                                     uint32_t changes) {
-    if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
+std::list<NotifyArgs> TouchpadInputMapper::reconfigure(nsecs_t when,
+                                                       const InputReaderConfiguration& config,
+                                                       ConfigurationChanges changes) {
+    if (!changes.any()) {
+        // First time configuration
+        mPropertyProvider.loadPropertiesFromIdcFile(getDeviceContext().getConfiguration());
+    }
+
+    if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) {
         std::optional<int32_t> displayId = mPointerController->getDisplayId();
         ui::Rotation orientation = ui::ROTATION_0;
         if (displayId.has_value()) {
-            if (auto viewport = config->getDisplayViewportById(*displayId); viewport) {
+            if (auto viewport = config.getDisplayViewportById(*displayId); viewport) {
                 orientation = getInverseRotation(viewport->orientation);
             }
         }
         mGestureConverter.setOrientation(orientation);
     }
-    if (!changes || (changes & InputReaderConfiguration::CHANGE_TOUCHPAD_SETTINGS)) {
+    if (!changes.any() || changes.test(InputReaderConfiguration::Change::TOUCHPAD_SETTINGS)) {
         mPropertyProvider.getProperty("Use Custom Touchpad Pointer Accel Curve")
                 .setBoolValues({true});
         GesturesProp accelCurveProp = mPropertyProvider.getProperty("Pointer Accel Curve");
         accelCurveProp.setRealValues(
-                createAccelerationCurveForSensitivity(config->touchpadPointerSpeed,
+                createAccelerationCurveForSensitivity(config.touchpadPointerSpeed,
                                                       accelCurveProp.getCount()));
+        mPropertyProvider.getProperty("Use Custom Touchpad Scroll Accel Curve")
+                .setBoolValues({true});
+        GesturesProp scrollCurveProp = mPropertyProvider.getProperty("Scroll Accel Curve");
+        scrollCurveProp.setRealValues(
+                createAccelerationCurveForSensitivity(config.touchpadPointerSpeed,
+                                                      scrollCurveProp.getCount()));
+        mPropertyProvider.getProperty("Scroll X Out Scale").setRealValues({1.0});
+        mPropertyProvider.getProperty("Scroll Y Out Scale").setRealValues({1.0});
         mPropertyProvider.getProperty("Invert Scrolling")
-                .setBoolValues({config->touchpadNaturalScrollingEnabled});
+                .setBoolValues({config.touchpadNaturalScrollingEnabled});
         mPropertyProvider.getProperty("Tap Enable")
-                .setBoolValues({config->touchpadTapToClickEnabled});
+                .setBoolValues({config.touchpadTapToClickEnabled});
         mPropertyProvider.getProperty("Button Right Click Zone Enable")
-                .setBoolValues({config->touchpadRightClickZoneEnabled});
+                .setBoolValues({config.touchpadRightClickZoneEnabled});
     }
-    return {};
+    std::list<NotifyArgs> out;
+    if ((!changes.any() && config.pointerCaptureRequest.enable) ||
+        changes.test(InputReaderConfiguration::Change::POINTER_CAPTURE)) {
+        mPointerCaptured = config.pointerCaptureRequest.enable;
+        // The motion ranges are going to change, so bump the generation to clear the cached ones.
+        bumpGeneration();
+        if (mPointerCaptured) {
+            // The touchpad is being captured, so we need to tidy up any fake fingers etc. that are
+            // still being reported for a gesture in progress.
+            out += reset(when);
+            mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
+        } else {
+            // We're transitioning from captured to uncaptured.
+            mCapturedEventConverter.reset();
+        }
+        if (changes.any()) {
+            out.push_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId()));
+        }
+    }
+    return out;
 }
 
 std::list<NotifyArgs> TouchpadInputMapper::reset(nsecs_t when) {
     mStateConverter.reset();
-    mGestureConverter.reset();
-    return InputMapper::reset(when);
+    resetGestureInterpreter(when);
+    std::list<NotifyArgs> out = mGestureConverter.reset(when);
+    out += InputMapper::reset(when);
+    return out;
+}
+
+void TouchpadInputMapper::resetGestureInterpreter(nsecs_t when) {
+    // The GestureInterpreter has no official reset method, but sending a HardwareState with no
+    // fingers down or buttons pressed should get it into a clean state.
+    HardwareState state;
+    state.timestamp = std::chrono::duration<stime_t>(std::chrono::nanoseconds(when)).count();
+    mResettingInterpreter = true;
+    mGestureInterpreter->PushHardwareState(&state);
+    mResettingInterpreter = false;
 }
 
 std::list<NotifyArgs> TouchpadInputMapper::process(const RawEvent* rawEvent) {
+    if (mPointerCaptured) {
+        return mCapturedEventConverter.process(*rawEvent);
+    }
     std::optional<SelfContainedHardwareState> state = mStateConverter.processRawEvent(rawEvent);
     if (state) {
         return sendHardwareState(rawEvent->when, rawEvent->readTime, *state);
@@ -270,6 +349,11 @@
 
 void TouchpadInputMapper::consumeGesture(const Gesture* gesture) {
     ALOGD_IF(DEBUG_TOUCHPAD_GESTURES, "Gesture ready: %s", gesture->String().c_str());
+    if (mResettingInterpreter) {
+        // We already handle tidying up fake fingers etc. in GestureConverter::reset, so we should
+        // ignore any gestures produced from the interpreter while we're resetting it.
+        return;
+    }
     if (!mProcessing) {
         ALOGE("Received gesture outside of the normal processing flow; ignoring it.");
         return;
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
index d693bca..23d0fd3 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -21,12 +21,15 @@
 #include <vector>
 
 #include <PointerControllerInterface.h>
+#include <utils/Timers.h>
 
+#include "CapturedTouchpadEventConverter.h"
 #include "EventHub.h"
 #include "InputDevice.h"
 #include "InputMapper.h"
 #include "InputReaderBase.h"
 #include "NotifyArgs.h"
+#include "accumulator/MultiTouchMotionAccumulator.h"
 #include "gestures/GestureConverter.h"
 #include "gestures/HardwareStateConverter.h"
 #include "gestures/PropertyProvider.h"
@@ -37,21 +40,28 @@
 
 class TouchpadInputMapper : public InputMapper {
 public:
-    explicit TouchpadInputMapper(InputDeviceContext& deviceContext);
+    template <class T, class... Args>
+    friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
+                                                const InputReaderConfiguration& readerConfig,
+                                                Args... args);
     ~TouchpadInputMapper();
 
     uint32_t getSources() const override;
+    void populateDeviceInfo(InputDeviceInfo& deviceInfo) override;
     void dump(std::string& dump) override;
 
-    [[nodiscard]] std::list<NotifyArgs> configure(nsecs_t when,
-                                                  const InputReaderConfiguration* config,
-                                                  uint32_t changes) override;
+    [[nodiscard]] std::list<NotifyArgs> reconfigure(nsecs_t when,
+                                                    const InputReaderConfiguration& config,
+                                                    ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
 
     void consumeGesture(const Gesture* gesture);
 
 private:
+    void resetGestureInterpreter(nsecs_t when);
+    explicit TouchpadInputMapper(InputDeviceContext& deviceContext,
+                                 const InputReaderConfiguration& readerConfig);
     [[nodiscard]] std::list<NotifyArgs> sendHardwareState(nsecs_t when, nsecs_t readTime,
                                                           SelfContainedHardwareState schs);
     [[nodiscard]] std::list<NotifyArgs> processGestures(nsecs_t when, nsecs_t readTime);
@@ -62,10 +72,19 @@
 
     PropertyProvider mPropertyProvider;
 
+    // The MultiTouchMotionAccumulator is shared between the HardwareStateConverter and
+    // CapturedTouchpadEventConverter, so that if the touchpad is captured or released while touches
+    // are down, the relevant converter can still benefit from the current axis values stored in the
+    // accumulator.
+    MultiTouchMotionAccumulator mMotionAccumulator;
+
     HardwareStateConverter mStateConverter;
     GestureConverter mGestureConverter;
+    CapturedTouchpadEventConverter mCapturedEventConverter;
 
+    bool mPointerCaptured = false;
     bool mProcessing = false;
+    bool mResettingInterpreter = false;
     std::vector<Gesture> mGesturesToProcess;
 };
 
diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
index 7645b12..8d78d0f 100644
--- a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
@@ -20,8 +20,9 @@
 
 namespace android {
 
-VibratorInputMapper::VibratorInputMapper(InputDeviceContext& deviceContext)
-      : InputMapper(deviceContext), mVibrating(false), mSequence(0) {}
+VibratorInputMapper::VibratorInputMapper(InputDeviceContext& deviceContext,
+                                         const InputReaderConfiguration& readerConfig)
+      : InputMapper(deviceContext, readerConfig), mVibrating(false), mSequence(0) {}
 
 VibratorInputMapper::~VibratorInputMapper() {}
 
@@ -29,10 +30,10 @@
     return 0;
 }
 
-void VibratorInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+void VibratorInputMapper::populateDeviceInfo(InputDeviceInfo& info) {
     InputMapper::populateDeviceInfo(info);
 
-    info->setVibrator(true);
+    info.setVibrator(true);
 }
 
 std::list<NotifyArgs> VibratorInputMapper::process(const RawEvent* rawEvent) {
diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.h b/services/inputflinger/reader/mapper/VibratorInputMapper.h
index e98f63a..9079c73 100644
--- a/services/inputflinger/reader/mapper/VibratorInputMapper.h
+++ b/services/inputflinger/reader/mapper/VibratorInputMapper.h
@@ -22,11 +22,14 @@
 
 class VibratorInputMapper : public InputMapper {
 public:
-    explicit VibratorInputMapper(InputDeviceContext& deviceContext);
+    template <class T, class... Args>
+    friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
+                                                const InputReaderConfiguration& readerConfig,
+                                                Args... args);
     virtual ~VibratorInputMapper();
 
     virtual uint32_t getSources() const override;
-    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
+    virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override;
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
 
     [[nodiscard]] std::list<NotifyArgs> vibrate(const VibrationSequence& sequence, ssize_t repeat,
@@ -45,6 +48,8 @@
     ssize_t mIndex;
     nsecs_t mNextStepTime;
 
+    explicit VibratorInputMapper(InputDeviceContext& deviceContext,
+                                 const InputReaderConfiguration& readerConfig);
     [[nodiscard]] std::list<NotifyArgs> nextStep();
     [[nodiscard]] NotifyVibratorStateArgs stopVibrating();
 };
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
index f6a42bd..f70be72 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
@@ -154,18 +154,18 @@
 
 // --- MultiTouchMotionAccumulator::Slot ---
 
-int32_t MultiTouchMotionAccumulator::Slot::getToolType() const {
+ToolType MultiTouchMotionAccumulator::Slot::getToolType() const {
     if (mHaveAbsMtToolType) {
         switch (mAbsMtToolType) {
             case MT_TOOL_FINGER:
-                return AMOTION_EVENT_TOOL_TYPE_FINGER;
+                return ToolType::FINGER;
             case MT_TOOL_PEN:
-                return AMOTION_EVENT_TOOL_TYPE_STYLUS;
+                return ToolType::STYLUS;
             case MT_TOOL_PALM:
-                return AMOTION_EVENT_TOOL_TYPE_PALM;
+                return ToolType::PALM;
         }
     }
-    return AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+    return ToolType::UNKNOWN;
 }
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
index 3c1a2a9..943dde5 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
@@ -45,7 +45,7 @@
         inline int32_t getTrackingId() const { return mAbsMtTrackingId; }
         inline int32_t getPressure() const { return mAbsMtPressure; }
         inline int32_t getDistance() const { return mAbsMtDistance; }
-        int32_t getToolType() const;
+        ToolType getToolType() const;
 
     private:
         friend class MultiTouchMotionAccumulator;
diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
index 6b84f32..8c4bed3 100644
--- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
@@ -141,21 +141,21 @@
     return result;
 }
 
-int32_t TouchButtonAccumulator::getToolType() const {
+ToolType TouchButtonAccumulator::getToolType() const {
     if (mBtnToolMouse || mBtnToolLens) {
-        return AMOTION_EVENT_TOOL_TYPE_MOUSE;
+        return ToolType::MOUSE;
     }
     if (mBtnToolRubber) {
-        return AMOTION_EVENT_TOOL_TYPE_ERASER;
+        return ToolType::ERASER;
     }
     if (mBtnToolPen || mBtnToolBrush || mBtnToolPencil || mBtnToolAirbrush) {
-        return AMOTION_EVENT_TOOL_TYPE_STYLUS;
+        return ToolType::STYLUS;
     }
     if (mBtnToolFinger || mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap ||
         mBtnToolQuintTap) {
-        return AMOTION_EVENT_TOOL_TYPE_FINGER;
+        return ToolType::FINGER;
     }
-    return AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+    return ToolType::UNKNOWN;
 }
 
 bool TouchButtonAccumulator::isToolActive() const {
diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h
index c2aa2ad..e829692 100644
--- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h
@@ -36,11 +36,17 @@
     void process(const RawEvent* rawEvent);
 
     uint32_t getButtonState() const;
-    int32_t getToolType() const;
+    ToolType getToolType() const;
     bool isToolActive() const;
     bool isHovering() const;
     bool hasStylus() const;
     bool hasButtonTouch() const;
+
+    /*
+     * Returns the number of touches reported by the device through its BTN_TOOL_FINGER and
+     * BTN_TOOL_*TAP "buttons". Note that this count includes touches reported with their
+     * ABS_MT_TOOL_TYPE set to MT_TOOL_PALM.
+     */
     int getTouchCount() const;
 
 private:
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index d636d44..7eca6fa 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -16,13 +16,14 @@
 
 #include "gestures/GestureConverter.h"
 
+#include <optional>
 #include <sstream>
 
 #include <android-base/stringprintf.h>
-#include <android/input.h>
 #include <ftl/enum.h>
 #include <linux/input-event-codes.h>
 #include <log/log_main.h>
+#include <ui/FloatRect.h>
 
 #include "TouchCursorInputMapperCommon.h"
 #include "input/Input.h"
@@ -71,8 +72,54 @@
     return out.str();
 }
 
-void GestureConverter::reset() {
-    mButtonState = 0;
+std::list<NotifyArgs> GestureConverter::reset(nsecs_t when) {
+    std::list<NotifyArgs> out;
+    switch (mCurrentClassification) {
+        case MotionClassification::TWO_FINGER_SWIPE:
+            out.push_back(endScroll(when, when));
+            break;
+        case MotionClassification::MULTI_FINGER_SWIPE:
+            out += handleMultiFingerSwipeLift(when, when);
+            break;
+        case MotionClassification::PINCH:
+            out += endPinch(when, when);
+            break;
+        case MotionClassification::NONE:
+            // When a button is pressed, the Gestures library always ends the current gesture,
+            // so we don't have to worry about the case where buttons need to be lifted during a
+            // pinch or swipe.
+            if (mButtonState) {
+                out += releaseAllButtons(when, when);
+            }
+            break;
+        default:
+            break;
+    }
+    mCurrentClassification = MotionClassification::NONE;
+    mDownTime = 0;
+    return out;
+}
+
+void GestureConverter::populateMotionRanges(InputDeviceInfo& info) const {
+    info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, SOURCE, 0.0f, 1.0f, 0, 0, 0);
+
+    // TODO(b/259547750): set this using the raw axis ranges from the touchpad when pointer capture
+    // is enabled.
+    if (std::optional<FloatRect> rect = mPointerController->getBounds(); rect.has_value()) {
+        info.addMotionRange(AMOTION_EVENT_AXIS_X, SOURCE, rect->left, rect->right, 0, 0, 0);
+        info.addMotionRange(AMOTION_EVENT_AXIS_Y, SOURCE, rect->top, rect->bottom, 0, 0, 0);
+    }
+
+    info.addMotionRange(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, SOURCE, -1.0f, 1.0f, 0, 0, 0);
+    info.addMotionRange(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, SOURCE, -1.0f, 1.0f, 0, 0, 0);
+
+    // The other axes that can be reported don't have ranges that are easy to define. RELATIVE_X/Y
+    // and GESTURE_SCROLL_X/Y_DISTANCE are the result of acceleration functions being applied to
+    // finger movements, so their maximum values can't simply be derived from the size of the
+    // touchpad. GESTURE_PINCH_SCALE_FACTOR's maximum value depends on the minimum finger separation
+    // that the pad can report, which cannot be determined from its raw axis information. (Assuming
+    // a minimum finger separation of 1 unit would let us calculate a theoretical maximum, but it
+    // would be orders of magnitude too high, so probably not very useful.)
 }
 
 std::list<NotifyArgs> GestureConverter::handleGesture(nsecs_t when, nsecs_t readTime,
@@ -85,7 +132,7 @@
         case kGestureTypeScroll:
             return handleScroll(when, readTime, gesture);
         case kGestureTypeFling:
-            return {handleFling(when, readTime, gesture)};
+            return handleFling(when, readTime, gesture);
         case kGestureTypeSwipe:
             return handleMultiFingerSwipe(when, readTime, 3, gesture.details.swipe.dx,
                                           gesture.details.swipe.dy);
@@ -98,12 +145,12 @@
         case kGestureTypePinch:
             return handlePinch(when, readTime, gesture);
         default:
-            // TODO(b/251196347): handle more gesture types.
             return {};
     }
 }
 
-NotifyArgs GestureConverter::handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture) {
+NotifyMotionArgs GestureConverter::handleMove(nsecs_t when, nsecs_t readTime,
+                                              const Gesture& gesture) {
     float deltaX = gesture.details.move.dx;
     float deltaY = gesture.details.move.dy;
     rotateDelta(mOrientation, &deltaX, &deltaY);
@@ -111,8 +158,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 +183,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();
@@ -198,22 +244,56 @@
     return out;
 }
 
+std::list<NotifyArgs> GestureConverter::releaseAllButtons(nsecs_t when, nsecs_t readTime) {
+    std::list<NotifyArgs> out;
+    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
+
+    PointerCoords coords;
+    coords.clear();
+    coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0);
+    const bool pointerDown = isPointerDown(mButtonState);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pointerDown ? 1.0f : 0.0f);
+    uint32_t newButtonState = mButtonState;
+    for (uint32_t button = AMOTION_EVENT_BUTTON_PRIMARY; button <= AMOTION_EVENT_BUTTON_FORWARD;
+         button <<= 1) {
+        if (mButtonState & button) {
+            newButtonState &= ~button;
+            out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+                                         button, newButtonState, /*pointerCount=*/1,
+                                         mFingerProps.data(), &coords, xCursorPosition,
+                                         yCursorPosition));
+        }
+    }
+    if (pointerDown) {
+        coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f);
+        out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /*actionButton=*/0,
+                                     newButtonState, /*pointerCount=*/1, mFingerProps.data(),
+                                     &coords, xCursorPosition, yCursorPosition));
+    }
+    mButtonState = 0;
+    return out;
+}
+
 std::list<NotifyArgs> GestureConverter::handleScroll(nsecs_t when, nsecs_t readTime,
                                                      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,29 +304,56 @@
     // 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;
 }
 
-NotifyArgs GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, const Gesture& gesture) {
-    // We don't actually want to use the gestures library's fling velocity values (to ensure
-    // consistency between touchscreen and touchpad flings), so we're just using the "start fling"
-    // gestures as a marker for the end of a two-finger scroll gesture.
-    if (gesture.details.fling.fling_state != GESTURES_FLING_START ||
-        mCurrentClassification != MotionClassification::TWO_FINGER_SWIPE) {
-        return {};
+std::list<NotifyArgs> GestureConverter::handleFling(nsecs_t when, nsecs_t readTime,
+                                                    const Gesture& gesture) {
+    switch (gesture.details.fling.fling_state) {
+        case GESTURES_FLING_START:
+            if (mCurrentClassification == MotionClassification::TWO_FINGER_SWIPE) {
+                // We don't actually want to use the gestures library's fling velocity values (to
+                // ensure consistency between touchscreen and touchpad flings), so we're just using
+                // the "start fling" gestures as a marker for the end of a two-finger scroll
+                // gesture.
+                return {endScroll(when, readTime)};
+            }
+            break;
+        case GESTURES_FLING_TAP_DOWN:
+            if (mCurrentClassification == MotionClassification::NONE) {
+                // Use the tap down state of a fling gesture as an indicator that a contact
+                // has been initiated with the touchpad. We treat this as a move event with zero
+                // magnitude, which will also result in the pointer icon being updated.
+                // TODO(b/282023644): Add a signal in libgestures for when a stable contact has been
+                //  initiated with a touchpad.
+                return {handleMove(when, readTime,
+                                   Gesture(kGestureMove, gesture.start_time, gesture.end_time,
+                                           /*dx=*/0.f,
+                                           /*dy=*/0.f))};
+            }
+            break;
+        default:
+            break;
     }
 
-    float xCursorPosition, yCursorPosition;
-    mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+    return {};
+}
+
+NotifyMotionArgs GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) {
+    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 +363,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 +399,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 +409,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 +425,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);
 
@@ -346,9 +448,7 @@
 
 [[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
@@ -371,6 +471,7 @@
         mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
         mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
         mDownTime = when;
+        std::list<NotifyArgs> out;
         out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
                                      /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
                                      mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
@@ -385,19 +486,7 @@
     }
 
     if (gesture.details.pinch.zoom_state == GESTURES_ZOOM_END) {
-        mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 1.0);
-        out.push_back(makeMotionArgs(when, readTime,
-                                     AMOTION_EVENT_ACTION_POINTER_UP |
-                                             1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT,
-                                     /* actionButton= */ 0, mButtonState, /* pointerCount= */ 2,
-                                     mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                     yCursorPosition));
-        out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0,
-                                     mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
-                                     mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
-        mCurrentClassification = MotionClassification::NONE;
-        mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 0);
-        return out;
+        return endPinch(when, readTime);
     }
 
     mPinchFingerSeparation *= gesture.details.pinch.dz;
@@ -409,9 +498,27 @@
     mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X,
                                       xCursorPosition + mPinchFingerSeparation / 2);
     mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
-    out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
-                                 mButtonState, /* pointerCount= */ 2, mFingerProps.data(),
+    return {makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /*actionButton=*/0,
+                           mButtonState, /*pointerCount=*/2, mFingerProps.data(),
+                           mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)};
+}
+
+std::list<NotifyArgs> GestureConverter::endPinch(nsecs_t when, nsecs_t readTime) {
+    std::list<NotifyArgs> out;
+    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
+
+    mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 1.0);
+    out.push_back(makeMotionArgs(when, readTime,
+                                 AMOTION_EVENT_ACTION_POINTER_UP |
+                                         1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT,
+                                 /*actionButton=*/0, mButtonState, /*pointerCount=*/2,
+                                 mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
+                                 yCursorPosition));
+    out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /*actionButton=*/0,
+                                 mButtonState, /*pointerCount=*/1, mFingerProps.data(),
                                  mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
+    mCurrentClassification = MotionClassification::NONE;
+    mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 0);
     return out;
 }
 
@@ -421,17 +528,29 @@
                                                   const PointerProperties* pointerProperties,
                                                   const PointerCoords* pointerCoords,
                                                   float xCursorPosition, float yCursorPosition) {
-    // TODO(b/260226362): consider what the appropriate source for these events is.
-    const uint32_t source = AINPUT_SOURCE_MOUSE;
-
-    return NotifyMotionArgs(mReaderContext.getNextId(), when, readTime, mDeviceId, source,
-                            mPointerController->getDisplayId(), /* policyFlags= */ POLICY_FLAG_WAKE,
-                            action, /* actionButton= */ actionButton, /* flags= */ 0,
-                            mReaderContext.getGlobalMetaState(), buttonState,
-                            mCurrentClassification, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount,
-                            pointerProperties, pointerCoords, /* xPrecision= */ 1.0f,
-                            /* yPrecision= */ 1.0f, xCursorPosition, yCursorPosition,
-                            /* downTime= */ mDownTime, /* videoFrames= */ {});
+    return {mReaderContext.getNextId(),
+            when,
+            readTime,
+            mDeviceId,
+            SOURCE,
+            mPointerController->getDisplayId(),
+            /* policyFlags= */ POLICY_FLAG_WAKE,
+            action,
+            /* actionButton= */ actionButton,
+            /* flags= */ 0,
+            mReaderContext.getGlobalMetaState(),
+            buttonState,
+            mCurrentClassification,
+            AMOTION_EVENT_EDGE_FLAG_NONE,
+            pointerCount,
+            pointerProperties,
+            pointerCoords,
+            /* xPrecision= */ 1.0f,
+            /* yPrecision= */ 1.0f,
+            xCursorPosition,
+            yCursorPosition,
+            /* downTime= */ mDownTime,
+            /* videoFrames= */ {}};
 }
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
index 2ec5841..b613b88 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
@@ -21,6 +21,7 @@
 #include <memory>
 
 #include <PointerControllerInterface.h>
+#include <android/input.h>
 #include <utils/Timers.h>
 
 #include "EventHub.h"
@@ -43,24 +44,32 @@
     std::string dump() const;
 
     void setOrientation(ui::Rotation orientation) { mOrientation = orientation; }
-    void reset();
+    [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when);
+
+    void populateMotionRanges(InputDeviceInfo& info) const;
 
     [[nodiscard]] std::list<NotifyArgs> handleGesture(nsecs_t when, nsecs_t readTime,
                                                       const Gesture& gesture);
 
 private:
-    [[nodiscard]] NotifyArgs handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture);
+    [[nodiscard]] NotifyMotionArgs handleMove(nsecs_t when, nsecs_t readTime,
+                                              const Gesture& gesture);
     [[nodiscard]] std::list<NotifyArgs> handleButtonsChange(nsecs_t when, nsecs_t readTime,
                                                             const Gesture& gesture);
+    [[nodiscard]] std::list<NotifyArgs> releaseAllButtons(nsecs_t when, nsecs_t readTime);
     [[nodiscard]] std::list<NotifyArgs> handleScroll(nsecs_t when, nsecs_t readTime,
                                                      const Gesture& gesture);
-    [[nodiscard]] NotifyArgs handleFling(nsecs_t when, nsecs_t readTime, const Gesture& gesture);
+    [[nodiscard]] std::list<NotifyArgs> handleFling(nsecs_t when, nsecs_t readTime,
+                                                    const Gesture& gesture);
+    [[nodiscard]] NotifyMotionArgs endScroll(nsecs_t when, nsecs_t readTime);
+
     [[nodiscard]] std::list<NotifyArgs> handleMultiFingerSwipe(nsecs_t when, nsecs_t readTime,
                                                                uint32_t fingerCount, float dx,
                                                                float dy);
     [[nodiscard]] std::list<NotifyArgs> handleMultiFingerSwipeLift(nsecs_t when, nsecs_t readTime);
     [[nodiscard]] std::list<NotifyArgs> handlePinch(nsecs_t when, nsecs_t readTime,
                                                     const Gesture& gesture);
+    [[nodiscard]] std::list<NotifyArgs> endPinch(nsecs_t when, nsecs_t readTime);
 
     NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
                                     int32_t actionButton, int32_t buttonState,
@@ -92,12 +101,15 @@
     // We never need any PointerProperties other than the finger tool type, so we can just keep a
     // const array of them.
     const std::array<PointerProperties, MAX_FAKE_FINGERS> mFingerProps = {{
-            {.id = 0, .toolType = AMOTION_EVENT_TOOL_TYPE_FINGER},
-            {.id = 1, .toolType = AMOTION_EVENT_TOOL_TYPE_FINGER},
-            {.id = 2, .toolType = AMOTION_EVENT_TOOL_TYPE_FINGER},
-            {.id = 3, .toolType = AMOTION_EVENT_TOOL_TYPE_FINGER},
+            {.id = 0, .toolType = ToolType::FINGER},
+            {.id = 1, .toolType = ToolType::FINGER},
+            {.id = 2, .toolType = ToolType::FINGER},
+            {.id = 3, .toolType = ToolType::FINGER},
     }};
     std::array<PointerCoords, MAX_FAKE_FINGERS> mFakeFingerCoords = {};
+
+    // TODO(b/260226362): consider what the appropriate source for these events is.
+    static constexpr uint32_t SOURCE = AINPUT_SOURCE_MOUSE;
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp
index c091a51..6780dce 100644
--- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+// clang-format off
+#include "../Macros.h"
+// clang-format on
 #include "gestures/HardwareStateConverter.h"
 
 #include <chrono>
@@ -23,16 +26,11 @@
 
 namespace android {
 
-HardwareStateConverter::HardwareStateConverter(const InputDeviceContext& deviceContext)
-      : mDeviceContext(deviceContext), mTouchButtonAccumulator(deviceContext) {
-    RawAbsoluteAxisInfo slotAxisInfo;
-    deviceContext.getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo);
-    if (!slotAxisInfo.valid || slotAxisInfo.maxValue <= 0) {
-        ALOGW("Touchpad \"%s\" doesn't have a valid ABS_MT_SLOT axis, and probably won't work "
-              "properly.",
-              deviceContext.getName().c_str());
-    }
-    mMotionAccumulator.configure(deviceContext, slotAxisInfo.maxValue + 1, true);
+HardwareStateConverter::HardwareStateConverter(const InputDeviceContext& deviceContext,
+                                               MultiTouchMotionAccumulator& motionAccumulator)
+      : mDeviceContext(deviceContext),
+        mMotionAccumulator(motionAccumulator),
+        mTouchButtonAccumulator(deviceContext) {
     mTouchButtonAccumulator.configure();
 }
 
@@ -78,16 +76,16 @@
     }
 
     schs.fingers.clear();
+    size_t numPalms = 0;
     for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) {
         MultiTouchMotionAccumulator::Slot slot = mMotionAccumulator.getSlot(i);
         if (!slot.isInUse()) {
             continue;
         }
         // Some touchpads continue to report contacts even after they've identified them as palms.
-        // We want to exclude these contacts from the HardwareStates, but still need to report a
-        // tracking ID of -1 if a finger turns into a palm.
-        const bool isPalm = slot.getToolType() == AMOTION_EVENT_TOOL_TYPE_PALM;
-        if (isPalm && mFingerSlots.find(i) == mFingerSlots.end()) {
+        // We want to exclude these contacts from the HardwareStates.
+        if (slot.getToolType() == ToolType::PALM) {
+            numPalms++;
             continue;
         }
 
@@ -101,23 +99,17 @@
         fingerState.orientation = slot.getOrientation();
         fingerState.position_x = slot.getX();
         fingerState.position_y = slot.getY();
-        fingerState.tracking_id = isPalm ? -1 : slot.getTrackingId();
-        if (fingerState.tracking_id == -1) {
-            mFingerSlots.erase(i);
-        } else {
-            mFingerSlots.insert(i);
-        }
+        fingerState.tracking_id = slot.getTrackingId();
     }
     schs.state.fingers = schs.fingers.data();
     schs.state.finger_cnt = schs.fingers.size();
-    schs.state.touch_cnt = mTouchButtonAccumulator.getTouchCount();
+    schs.state.touch_cnt = mTouchButtonAccumulator.getTouchCount() - numPalms;
     return schs;
 }
 
 void HardwareStateConverter::reset() {
     mCursorButtonAccumulator.reset(mDeviceContext);
     mTouchButtonAccumulator.reset();
-    mFingerSlots.clear();
     mMscTimestamp = 0;
 }
 
diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
index d6787b7..633448e 100644
--- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
@@ -41,7 +41,8 @@
 // Converts RawEvents into the HardwareState structs used by the gestures library.
 class HardwareStateConverter {
 public:
-    HardwareStateConverter(const InputDeviceContext& deviceContext);
+    HardwareStateConverter(const InputDeviceContext& deviceContext,
+                           MultiTouchMotionAccumulator& motionAccumulator);
 
     std::optional<SelfContainedHardwareState> processRawEvent(const RawEvent* event);
     void reset();
@@ -51,10 +52,9 @@
 
     const InputDeviceContext& mDeviceContext;
     CursorButtonAccumulator mCursorButtonAccumulator;
-    MultiTouchMotionAccumulator mMotionAccumulator;
+    MultiTouchMotionAccumulator& mMotionAccumulator;
     TouchButtonAccumulator mTouchButtonAccumulator;
     int32_t mMscTimestamp = 0;
-    std::set<size_t> mFingerSlots;
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp
index 089f45a..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>
@@ -84,6 +85,29 @@
     return dump;
 }
 
+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] =
@@ -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 50451a3..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 {
 
@@ -35,6 +36,8 @@
     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,
                                          const int* init);
@@ -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 97138c7..52277ff 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -39,6 +39,7 @@
     srcs: [
         "AnrTracker_test.cpp",
         "BlockingQueue_test.cpp",
+        "CapturedTouchpadEventConverter_test.cpp",
         "EventHub_test.cpp",
         "FakeEventHub.cpp",
         "FakeInputReaderPolicy.cpp",
diff --git a/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp b/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp
new file mode 100644
index 0000000..99a6a1f
--- /dev/null
+++ b/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp
@@ -0,0 +1,955 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <CapturedTouchpadEventConverter.h>
+
+#include <list>
+#include <memory>
+
+#include <EventHub.h>
+#include <gtest/gtest.h>
+#include <linux/input-event-codes.h>
+#include <linux/input.h>
+#include <utils/StrongPointer.h>
+
+#include "FakeEventHub.h"
+#include "FakeInputReaderPolicy.h"
+#include "InstrumentedInputReader.h"
+#include "TestConstants.h"
+#include "TestInputListener.h"
+#include "TestInputListenerMatchers.h"
+
+namespace android {
+
+using testing::AllOf;
+
+class CapturedTouchpadEventConverterTest : public testing::Test {
+public:
+    CapturedTouchpadEventConverterTest()
+          : mFakeEventHub(std::make_unique<FakeEventHub>()),
+            mFakePolicy(sp<FakeInputReaderPolicy>::make()),
+            mReader(mFakeEventHub, mFakePolicy, mFakeListener),
+            mDevice(newDevice()),
+            mDeviceContext(*mDevice, EVENTHUB_ID) {
+        const size_t slotCount = 8;
+        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_SLOT, 0, slotCount - 1, 0, 0, 0);
+        mAccumulator.configure(mDeviceContext, slotCount, /*usingSlotsProtocol=*/true);
+    }
+
+protected:
+    static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
+    static constexpr int32_t EVENTHUB_ID = 1;
+
+    std::shared_ptr<InputDevice> newDevice() {
+        InputDeviceIdentifier identifier;
+        identifier.name = "device";
+        identifier.location = "USB1";
+        identifier.bus = 0;
+        std::shared_ptr<InputDevice> device =
+                std::make_shared<InputDevice>(mReader.getContext(), DEVICE_ID, /*generation=*/2,
+                                              identifier);
+        mReader.pushNextDevice(device);
+        mFakeEventHub->addDevice(EVENTHUB_ID, identifier.name, InputDeviceClass::TOUCHPAD,
+                                 identifier.bus);
+        mReader.loopOnce();
+        return device;
+    }
+
+    void addBasicAxesToEventHub() {
+        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45);
+        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40);
+        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_PRESSURE, 0, 256, 0, 0, 0);
+        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MAJOR, 0, 1000, 0, 0, 0);
+        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MINOR, 0, 1000, 0, 0, 0);
+    }
+
+    CapturedTouchpadEventConverter createConverter() {
+        addBasicAxesToEventHub();
+        return CapturedTouchpadEventConverter(*mReader.getContext(), mDeviceContext, mAccumulator,
+                                              DEVICE_ID);
+    }
+
+    void processAxis(CapturedTouchpadEventConverter& conv, int32_t type, int32_t code,
+                     int32_t value) {
+        RawEvent event;
+        event.when = ARBITRARY_TIME;
+        event.readTime = READ_TIME;
+        event.deviceId = EVENTHUB_ID;
+        event.type = type;
+        event.code = code;
+        event.value = value;
+        std::list<NotifyArgs> out = conv.process(event);
+        EXPECT_TRUE(out.empty());
+    }
+
+    std::list<NotifyArgs> processSync(CapturedTouchpadEventConverter& conv) {
+        RawEvent event;
+        event.when = ARBITRARY_TIME;
+        event.readTime = READ_TIME;
+        event.deviceId = EVENTHUB_ID;
+        event.type = EV_SYN;
+        event.code = SYN_REPORT;
+        event.value = 0;
+        return conv.process(event);
+    }
+
+    NotifyMotionArgs processSyncAndExpectSingleMotionArg(CapturedTouchpadEventConverter& conv) {
+        std::list<NotifyArgs> args = processSync(conv);
+        EXPECT_EQ(1u, args.size());
+        return std::get<NotifyMotionArgs>(args.front());
+    }
+
+    std::shared_ptr<FakeEventHub> mFakeEventHub;
+    sp<FakeInputReaderPolicy> mFakePolicy;
+    TestInputListener mFakeListener;
+    InstrumentedInputReader mReader;
+    std::shared_ptr<InputDevice> mDevice;
+    InputDeviceContext mDeviceContext;
+    MultiTouchMotionAccumulator mAccumulator;
+};
+
+TEST_F(CapturedTouchpadEventConverterTest, MotionRanges_allAxesPresent_populatedCorrectly) {
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MAJOR, 0, 1100, 0, 0, 35);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MINOR, 0, 1000, 0, 0, 30);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 900, 0, 0, 25);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MINOR, 0, 800, 0, 0, 20);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_ORIENTATION, -3, 4, 0, 0, 0);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_PRESSURE, 0, 256, 0, 0, 0);
+    CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+                                        DEVICE_ID);
+
+    InputDeviceInfo info;
+    conv.populateMotionRanges(info);
+
+    // Most axes should have min, max, and resolution matching the evdev axes.
+    const InputDeviceInfo::MotionRange* posX =
+            info.getMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHPAD);
+    ASSERT_NE(nullptr, posX);
+    EXPECT_NEAR(0, posX->min, EPSILON);
+    EXPECT_NEAR(4000, posX->max, EPSILON);
+    EXPECT_NEAR(45, posX->resolution, EPSILON);
+
+    const InputDeviceInfo::MotionRange* posY =
+            info.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHPAD);
+    ASSERT_NE(nullptr, posY);
+    EXPECT_NEAR(0, posY->min, EPSILON);
+    EXPECT_NEAR(2500, posY->max, EPSILON);
+    EXPECT_NEAR(40, posY->resolution, EPSILON);
+
+    const InputDeviceInfo::MotionRange* touchMajor =
+            info.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MAJOR, AINPUT_SOURCE_TOUCHPAD);
+    ASSERT_NE(nullptr, touchMajor);
+    EXPECT_NEAR(0, touchMajor->min, EPSILON);
+    EXPECT_NEAR(1100, touchMajor->max, EPSILON);
+    EXPECT_NEAR(35, touchMajor->resolution, EPSILON);
+
+    const InputDeviceInfo::MotionRange* touchMinor =
+            info.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MINOR, AINPUT_SOURCE_TOUCHPAD);
+    ASSERT_NE(nullptr, touchMinor);
+    EXPECT_NEAR(0, touchMinor->min, EPSILON);
+    EXPECT_NEAR(1000, touchMinor->max, EPSILON);
+    EXPECT_NEAR(30, touchMinor->resolution, EPSILON);
+
+    const InputDeviceInfo::MotionRange* toolMajor =
+            info.getMotionRange(AMOTION_EVENT_AXIS_TOOL_MAJOR, AINPUT_SOURCE_TOUCHPAD);
+    ASSERT_NE(nullptr, toolMajor);
+    EXPECT_NEAR(0, toolMajor->min, EPSILON);
+    EXPECT_NEAR(900, toolMajor->max, EPSILON);
+    EXPECT_NEAR(25, toolMajor->resolution, EPSILON);
+
+    const InputDeviceInfo::MotionRange* toolMinor =
+            info.getMotionRange(AMOTION_EVENT_AXIS_TOOL_MINOR, AINPUT_SOURCE_TOUCHPAD);
+    ASSERT_NE(nullptr, toolMinor);
+    EXPECT_NEAR(0, toolMinor->min, EPSILON);
+    EXPECT_NEAR(800, toolMinor->max, EPSILON);
+    EXPECT_NEAR(20, toolMinor->resolution, EPSILON);
+
+    // ...except orientation and pressure, which get scaled, and size, which is generated from other
+    // values.
+    const InputDeviceInfo::MotionRange* orientation =
+            info.getMotionRange(AMOTION_EVENT_AXIS_ORIENTATION, AINPUT_SOURCE_TOUCHPAD);
+    ASSERT_NE(nullptr, orientation);
+    EXPECT_NEAR(-M_PI_2, orientation->min, EPSILON);
+    EXPECT_NEAR(M_PI_2, orientation->max, EPSILON);
+    EXPECT_NEAR(0, orientation->resolution, EPSILON);
+
+    const InputDeviceInfo::MotionRange* pressure =
+            info.getMotionRange(AMOTION_EVENT_AXIS_PRESSURE, AINPUT_SOURCE_TOUCHPAD);
+    ASSERT_NE(nullptr, pressure);
+    EXPECT_NEAR(0, pressure->min, EPSILON);
+    EXPECT_NEAR(1, pressure->max, EPSILON);
+    EXPECT_NEAR(0, pressure->resolution, EPSILON);
+
+    const InputDeviceInfo::MotionRange* size =
+            info.getMotionRange(AMOTION_EVENT_AXIS_SIZE, AINPUT_SOURCE_TOUCHPAD);
+    ASSERT_NE(nullptr, size);
+    EXPECT_NEAR(0, size->min, EPSILON);
+    EXPECT_NEAR(1, size->max, EPSILON);
+    EXPECT_NEAR(0, size->resolution, EPSILON);
+}
+
+TEST_F(CapturedTouchpadEventConverterTest, MotionRanges_bareMinimumAxesPresent_populatedCorrectly) {
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40);
+    CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+                                        DEVICE_ID);
+
+    InputDeviceInfo info;
+    conv.populateMotionRanges(info);
+
+    // Only the bare minimum motion ranges should be reported, and no others (e.g. size shouldn't be
+    // present, since it's generated from axes that aren't provided by this device).
+    EXPECT_NE(nullptr, info.getMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHPAD));
+    EXPECT_NE(nullptr, info.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHPAD));
+    EXPECT_EQ(2u, info.getMotionRanges().size());
+}
+
+TEST_F(CapturedTouchpadEventConverterTest, OneFinger_motionReportedCorrectly) {
+    CapturedTouchpadEventConverter conv = createConverter();
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100);
+
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
+                      WithCoords(50, 100), WithToolType(ToolType::FINGER)));
+
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 99);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
+                      WithCoords(52, 99), WithToolType(ToolType::FINGER)));
+
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 0);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0);
+
+    std::list<NotifyArgs> args = processSync(conv);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
+                      WithCoords(52, 99), WithToolType(ToolType::FINGER)));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithPointerCount(1u),
+                      WithCoords(52, 99), WithToolType(ToolType::FINGER)));
+}
+
+TEST_F(CapturedTouchpadEventConverterTest, OneFinger_touchDimensionsPassedThrough) {
+    addBasicAxesToEventHub();
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 1000, 0, 0, 0);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MINOR, 0, 1000, 0, 0, 0);
+    CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+                                        DEVICE_ID);
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_TOUCH_MAJOR, 250);
+    processAxis(conv, EV_ABS, ABS_MT_TOUCH_MINOR, 120);
+    processAxis(conv, EV_ABS, ABS_MT_WIDTH_MAJOR, 400);
+    processAxis(conv, EV_ABS, ABS_MT_WIDTH_MINOR, 200);
+
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
+                      WithTouchDimensions(250, 120), WithToolDimensions(400, 200)));
+}
+
+TEST_F(CapturedTouchpadEventConverterTest, OneFinger_orientationCalculatedCorrectly) {
+    addBasicAxesToEventHub();
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_ORIENTATION, -3, 4, 0, 0, 0);
+    CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+                                        DEVICE_ID);
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_ORIENTATION, -3);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    EXPECT_NEAR(-3 * M_PI / 8,
+                processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue(
+                        AMOTION_EVENT_AXIS_ORIENTATION),
+                EPSILON);
+
+    processAxis(conv, EV_ABS, ABS_MT_ORIENTATION, 0);
+
+    EXPECT_NEAR(0,
+                processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue(
+                        AMOTION_EVENT_AXIS_ORIENTATION),
+                EPSILON);
+
+    processAxis(conv, EV_ABS, ABS_MT_ORIENTATION, 4);
+
+    EXPECT_NEAR(M_PI / 2,
+                processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue(
+                        AMOTION_EVENT_AXIS_ORIENTATION),
+                EPSILON);
+}
+
+TEST_F(CapturedTouchpadEventConverterTest, OneFinger_pressureScaledCorrectly) {
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_PRESSURE, 0, 256, 0, 0, 0);
+    CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+                                        DEVICE_ID);
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_PRESSURE, 128);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), WithPressure(0.5));
+}
+
+TEST_F(CapturedTouchpadEventConverterTest,
+       OneFinger_withAllSizeAxes_sizeCalculatedFromTouchMajorMinorAverage) {
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MAJOR, 0, 256, 0, 0, 0);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MINOR, 0, 256, 0, 0, 0);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 256, 0, 0, 0);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MINOR, 0, 256, 0, 0, 0);
+    CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+                                        DEVICE_ID);
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_TOUCH_MAJOR, 138);
+    processAxis(conv, EV_ABS, ABS_MT_TOUCH_MINOR, 118);
+    processAxis(conv, EV_ABS, ABS_MT_WIDTH_MAJOR, 200);
+    processAxis(conv, EV_ABS, ABS_MT_WIDTH_MINOR, 210);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    EXPECT_NEAR(0.5,
+                processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue(
+                        AMOTION_EVENT_AXIS_SIZE),
+                EPSILON);
+}
+
+TEST_F(CapturedTouchpadEventConverterTest,
+       OneFinger_withMajorDimensionsOnly_sizeCalculatedFromTouchMajor) {
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MAJOR, 0, 256, 0, 0, 0);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 256, 0, 0, 0);
+    CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+                                        DEVICE_ID);
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_TOUCH_MAJOR, 128);
+    processAxis(conv, EV_ABS, ABS_MT_WIDTH_MAJOR, 200);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    EXPECT_NEAR(0.5,
+                processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue(
+                        AMOTION_EVENT_AXIS_SIZE),
+                EPSILON);
+}
+
+TEST_F(CapturedTouchpadEventConverterTest,
+       OneFinger_withToolDimensionsOnly_sizeCalculatedFromToolMajorMinorAverage) {
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 256, 0, 0, 0);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MINOR, 0, 256, 0, 0, 0);
+    CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+                                        DEVICE_ID);
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_WIDTH_MAJOR, 138);
+    processAxis(conv, EV_ABS, ABS_MT_WIDTH_MINOR, 118);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    EXPECT_NEAR(0.5,
+                processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue(
+                        AMOTION_EVENT_AXIS_SIZE),
+                EPSILON);
+}
+
+TEST_F(CapturedTouchpadEventConverterTest,
+       OneFinger_withToolMajorOnly_sizeCalculatedFromTouchMajor) {
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 256, 0, 0, 0);
+    CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+                                        DEVICE_ID);
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_WIDTH_MAJOR, 128);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    EXPECT_NEAR(0.5,
+                processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue(
+                        AMOTION_EVENT_AXIS_SIZE),
+                EPSILON);
+}
+
+TEST_F(CapturedTouchpadEventConverterTest, OnePalm_neverReported) {
+    addBasicAxesToEventHub();
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOOL_TYPE, 0, MT_TOOL_PALM, 0, 0, 0);
+    CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+                                        DEVICE_ID);
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100);
+    processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    EXPECT_EQ(0u, processSync(conv).size());
+
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 51);
+
+    EXPECT_EQ(0u, processSync(conv).size());
+
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 0);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0);
+
+    EXPECT_EQ(0u, processSync(conv).size());
+}
+
+TEST_F(CapturedTouchpadEventConverterTest, FingerTurningIntoPalm_cancelled) {
+    addBasicAxesToEventHub();
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOOL_TYPE, 0, MT_TOOL_PALM, 0, 0, 0);
+    CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+                                        DEVICE_ID);
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100);
+    processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithToolType(ToolType::FINGER),
+                      WithPointerCount(1u)));
+
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 51);
+    processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM);
+
+    std::list<NotifyArgs> args = processSync(conv);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u)));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL), WithPointerCount(1u)));
+
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52);
+
+    EXPECT_EQ(0u, processSync(conv).size());
+
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 0);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0);
+
+    EXPECT_EQ(0u, processSync(conv).size());
+}
+
+TEST_F(CapturedTouchpadEventConverterTest, PalmTurningIntoFinger_reported) {
+    addBasicAxesToEventHub();
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOOL_TYPE, 0, MT_TOOL_PALM, 0, 0, 0);
+    CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+                                        DEVICE_ID);
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100);
+    processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    EXPECT_EQ(0u, processSync(conv).size());
+
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 51);
+    processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
+                      WithCoords(51, 100)));
+
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
+                      WithCoords(52, 100)));
+}
+
+TEST_F(CapturedTouchpadEventConverterTest, FingerArrivingAfterPalm_onlyFingerReported) {
+    addBasicAxesToEventHub();
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOOL_TYPE, 0, MT_TOOL_PALM, 0, 0, 0);
+    CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+                                        DEVICE_ID);
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100);
+    processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    EXPECT_EQ(0u, processSync(conv).size());
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 1);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 2);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 100);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 150);
+    processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0);
+    processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
+                      WithCoords(100, 150)));
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 102);
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 98);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 148);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
+                      WithCoords(98, 148)));
+}
+
+TEST_F(CapturedTouchpadEventConverterTest, FingerAndFingerTurningIntoPalm_partiallyCancelled) {
+    addBasicAxesToEventHub();
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOOL_TYPE, 0, MT_TOOL_PALM, 0, 0, 0);
+    CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+                                        DEVICE_ID);
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER);
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 1);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 2);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 250);
+    processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER);
+
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
+
+    std::list<NotifyArgs> args = processSync(conv);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
+                      WithToolType(ToolType::FINGER)));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithPointerCount(2u), WithPointerToolType(0, ToolType::FINGER),
+                      WithPointerToolType(1, ToolType::FINGER)));
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 51);
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 251);
+    processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM);
+
+    args = processSync(conv);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(2u)));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithFlags(AMOTION_EVENT_FLAG_CANCELED), WithPointerCount(2u)));
+}
+
+TEST_F(CapturedTouchpadEventConverterTest, FingerAndPalmTurningIntoFinger_reported) {
+    addBasicAxesToEventHub();
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOOL_TYPE, 0, MT_TOOL_PALM, 0, 0, 0);
+    CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+                                        DEVICE_ID);
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER);
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 1);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 2);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 250);
+    processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM);
+
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
+                      WithToolType(ToolType::FINGER)));
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 51);
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 251);
+    processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER);
+
+    std::list<NotifyArgs> args = processSync(conv);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u)));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithPointerCount(2u)));
+}
+
+TEST_F(CapturedTouchpadEventConverterTest, TwoFingers_motionReportedCorrectly) {
+    CapturedTouchpadEventConverter conv = createConverter();
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100);
+
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
+                      WithCoords(50, 100), WithToolType(ToolType::FINGER)));
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 99);
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 1);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 2);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 250);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 200);
+
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0);
+    processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
+
+    std::list<NotifyArgs> args = processSync(conv);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
+                      WithCoords(52, 99), WithToolType(ToolType::FINGER)));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithPointerCount(2u), WithPointerCoords(0, 52, 99),
+                      WithPointerCoords(1, 250, 200), WithPointerToolType(0, ToolType::FINGER),
+                      WithPointerToolType(1, ToolType::FINGER)));
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 255);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 202);
+
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
+
+    args = processSync(conv);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(2u),
+                      WithPointerCoords(0, 52, 99), WithPointerCoords(1, 255, 202),
+                      WithPointerToolType(1, ToolType::FINGER),
+                      WithPointerToolType(0, ToolType::FINGER)));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithPointerCount(2u), WithPointerCoords(0, 52, 99),
+                      WithPointerCoords(1, 255, 202), WithPointerToolType(0, ToolType::FINGER),
+                      WithPointerToolType(1, ToolType::FINGER)));
+
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 0);
+
+    args = processSync(conv);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
+                      WithCoords(255, 202), WithToolType(ToolType::FINGER)));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithPointerCount(1u),
+                      WithCoords(255, 202), WithToolType(ToolType::FINGER)));
+}
+
+// Pointer IDs max out at 31, and so must be reused once a touch is lifted to avoid running out.
+TEST_F(CapturedTouchpadEventConverterTest, PointerIdsReusedAfterLift) {
+    CapturedTouchpadEventConverter conv = createConverter();
+
+    // Put down two fingers, which should get IDs 0 and 1.
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 10);
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 1);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 2);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 20);
+
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
+
+    std::list<NotifyArgs> args = processSync(conv);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
+                      WithPointerId(/*index=*/0, /*id=*/0)));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithPointerCount(2u), WithPointerId(/*index=*/0, /*id=*/0),
+                      WithPointerId(/*index=*/1, /*id=*/1)));
+
+    // Lift the finger in slot 0, freeing up pointer ID 0...
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
+
+    // ...and simultaneously add a finger in slot 2.
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 2);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 3);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 30);
+
+    args = processSync(conv);
+    ASSERT_EQ(3u, args.size());
+    // Slot 1 being present will result in a MOVE event, even though it hasn't actually moved (see
+    // comments in CapturedTouchpadEventConverter::sync).
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(2u),
+                      WithPointerId(/*index=*/0, /*id=*/0), WithPointerId(/*index=*/1, /*id=*/1)));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithPointerCount(2u), WithPointerId(/*index=*/0, /*id=*/0),
+                      WithPointerId(/*index=*/1, /*id=*/1)));
+    args.pop_front();
+    // Slot 0 being lifted causes the finger from slot 1 to move up to index 0, but keep its
+    // previous ID. The new finger in slot 2 should take ID 0, which was just freed up.
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithPointerCount(2u), WithPointerId(/*index=*/0, /*id=*/1),
+                      WithPointerId(/*index=*/1, /*id=*/0)));
+}
+
+// Motion events without any pointers are invalid, so when a button press is reported in the same
+// frame as a touch down, the button press must be reported second. Similarly with a button release
+// and a touch lift.
+TEST_F(CapturedTouchpadEventConverterTest,
+       ButtonPressedAndReleasedInSameFrameAsTouch_ReportedWithPointers) {
+    CapturedTouchpadEventConverter conv = createConverter();
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    processAxis(conv, EV_KEY, BTN_LEFT, 1);
+
+    std::list<NotifyArgs> args = processSync(conv);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), WithPointerCount(1u),
+                      WithCoords(50, 100), WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY)));
+
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 0);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0);
+
+    processAxis(conv, EV_KEY, BTN_LEFT, 0);
+    args = processSync(conv);
+    ASSERT_EQ(3u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), WithPointerCount(1u),
+                      WithCoords(50, 100), WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithButtonState(0)));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_UP));
+}
+
+// Some touchpads sometimes report a button press before they report the finger touching the pad. In
+// that case we need to wait until the touch comes to report the button press.
+TEST_F(CapturedTouchpadEventConverterTest, ButtonPressedBeforeTouch_ReportedOnceTouchOccurs) {
+    CapturedTouchpadEventConverter conv = createConverter();
+
+    processAxis(conv, EV_KEY, BTN_LEFT, 1);
+    ASSERT_EQ(0u, processSync(conv).size());
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    std::list<NotifyArgs> args = processSync(conv);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), WithPointerCount(1u),
+                      WithCoords(50, 100), WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY)));
+}
+
+// When all fingers are lifted from a touchpad, we should release any buttons that are down, since
+// we won't be able to report them being lifted later if no pointers are present.
+TEST_F(CapturedTouchpadEventConverterTest, ButtonReleasedAfterTouchLifts_ReportedWithLift) {
+    CapturedTouchpadEventConverter conv = createConverter();
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    processAxis(conv, EV_KEY, BTN_LEFT, 1);
+
+    std::list<NotifyArgs> args = processSync(conv);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
+
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 0);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0);
+    args = processSync(conv);
+    ASSERT_EQ(3u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), WithPointerCount(1u),
+                      WithCoords(50, 100), WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithButtonState(0)));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_UP));
+
+    processAxis(conv, EV_KEY, BTN_LEFT, 0);
+    ASSERT_EQ(0u, processSync(conv).size());
+}
+
+TEST_F(CapturedTouchpadEventConverterTest, MultipleButtonsPressedDuringTouch_ReportedCorrectly) {
+    CapturedTouchpadEventConverter conv = createConverter();
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+                WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+
+    processAxis(conv, EV_KEY, BTN_LEFT, 1);
+    std::list<NotifyArgs> args = processSync(conv);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY)));
+
+    processAxis(conv, EV_KEY, BTN_RIGHT, 1);
+    args = processSync(conv);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
+                                      AMOTION_EVENT_BUTTON_SECONDARY)));
+
+    processAxis(conv, EV_KEY, BTN_LEFT, 0);
+    args = processSync(conv);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY)));
+
+    processAxis(conv, EV_KEY, BTN_RIGHT, 0);
+    args = processSync(conv);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0)));
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/FakeEventHub.cpp b/services/inputflinger/tests/FakeEventHub.cpp
index 6ac0bfb..212fceb 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,
@@ -593,4 +594,35 @@
     return lightIt->second;
 };
 
+void FakeEventHub::setSysfsRootPath(int32_t deviceId, std::string sysfsRootPath) const {
+    Device* device = getDevice(deviceId);
+    if (device == nullptr) {
+        return;
+    }
+    device->sysfsRootPath = sysfsRootPath;
+}
+
+void FakeEventHub::sysfsNodeChanged(const std::string& sysfsNodePath) {
+    int32_t foundDeviceId = -1;
+    Device* foundDevice = nullptr;
+    for (size_t i = 0; i < mDevices.size(); i++) {
+        Device* d = mDevices.valueAt(i);
+        if (sysfsNodePath.find(d->sysfsRootPath) != std::string::npos) {
+            foundDeviceId = mDevices.keyAt(i);
+            foundDevice = d;
+        }
+    }
+    if (foundDevice == nullptr) {
+        return;
+    }
+    // If device sysfs changed -> reopen the device
+    if (!mRawLightInfos.empty() && !foundDevice->classes.test(InputDeviceClass::LIGHT)) {
+        InputDeviceIdentifier identifier = foundDevice->identifier;
+        ftl::Flags<InputDeviceClass> classes = foundDevice->classes;
+        removeDevice(foundDeviceId);
+        addDevice(foundDeviceId, identifier.name, classes | InputDeviceClass::LIGHT,
+                  identifier.bus);
+    }
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/FakeEventHub.h b/services/inputflinger/tests/FakeEventHub.h
index 72f8ac0..8e06940 100644
--- a/services/inputflinger/tests/FakeEventHub.h
+++ b/services/inputflinger/tests/FakeEventHub.h
@@ -64,6 +64,7 @@
         std::vector<VirtualKeyDefinition> virtualKeys;
         bool enabled;
         std::optional<RawLayoutInfo> layoutInfo;
+        std::string sysfsRootPath;
 
         status_t enable() {
             enabled = true;
@@ -152,6 +153,7 @@
     void enqueueEvent(nsecs_t when, nsecs_t readTime, int32_t deviceId, int32_t type, int32_t code,
                       int32_t value);
     void assertQueueIsEmpty();
+    void setSysfsRootPath(int32_t deviceId, std::string sysfsRootPath) const;
 
 private:
     Device* getDevice(int32_t deviceId) const;
@@ -159,7 +161,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;
@@ -212,7 +214,7 @@
     std::optional<int32_t> getLightBrightness(int32_t deviceId, int32_t lightId) const override;
     std::optional<std::unordered_map<LightColor, int32_t>> getLightIntensities(
             int32_t deviceId, int32_t lightId) const override;
-
+    void sysfsNodeChanged(const std::string& sysfsNodePath) override;
     void dump(std::string&) const override {}
     void monitor() const override {}
     void requestReopenDevices() override {}
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index 30c1719..3486d0f 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -154,8 +154,8 @@
     mPointerController = std::move(controller);
 }
 
-const InputReaderConfiguration* FakeInputReaderPolicy::getReaderConfiguration() const {
-    return &mConfig;
+const InputReaderConfiguration& FakeInputReaderPolicy::getReaderConfiguration() const {
+    return mConfig;
 }
 
 const std::vector<InputDeviceInfo>& FakeInputReaderPolicy::getInputDevices() const {
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
index 28ac505..85ff01a 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.h
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -63,7 +63,7 @@
     void addDisabledDevice(int32_t deviceId);
     void removeDisabledDevice(int32_t deviceId);
     void setPointerController(std::shared_ptr<FakePointerController> controller);
-    const InputReaderConfiguration* getReaderConfiguration() const;
+    const InputReaderConfiguration& getReaderConfiguration() const;
     const std::vector<InputDeviceInfo>& getInputDevices() const;
     TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor,
                                                            ui::Rotation surfaceRotation);
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/FocusResolver_test.cpp b/services/inputflinger/tests/FocusResolver_test.cpp
index ccdb37a..5440a98 100644
--- a/services/inputflinger/tests/FocusResolver_test.cpp
+++ b/services/inputflinger/tests/FocusResolver_test.cpp
@@ -237,7 +237,60 @@
     ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken);
 }
 
-TEST(FocusResolverTest, ConditionalFocusRequestsAreNotPersistent) {
+TEST(FocusResolverTest, FocusTransferTarget) {
+    sp<IBinder> hostWindowToken = sp<BBinder>::make();
+    std::vector<sp<WindowInfoHandle>> windows;
+
+    sp<FakeWindowHandle> hostWindow =
+            sp<FakeWindowHandle>::make("Host Window", hostWindowToken, /*focusable=*/true,
+                                       /*visible=*/true);
+    windows.push_back(hostWindow);
+    sp<IBinder> embeddedWindowToken = sp<BBinder>::make();
+    sp<FakeWindowHandle> embeddedWindow =
+            sp<FakeWindowHandle>::make("Embedded Window", embeddedWindowToken, /*focusable=*/false,
+                                       /*visible=*/true);
+    windows.push_back(embeddedWindow);
+
+    FocusRequest request;
+    request.displayId = 42;
+    request.token = hostWindowToken;
+
+    // Host wants to transfer touch to embedded.
+    hostWindow->editInfo()->focusTransferTarget = embeddedWindowToken;
+
+    FocusResolver focusResolver;
+    std::optional<FocusResolver::FocusChanges> changes =
+            focusResolver.setFocusedWindow(request, windows);
+    // Embedded was not focusable so host gains focus.
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ hostWindowToken);
+
+    // Embedded is now focusable so will gain focus
+    embeddedWindow->setFocusable(true);
+    changes = focusResolver.setInputWindows(request.displayId, windows);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ hostWindowToken, /*to*/ embeddedWindowToken);
+
+    // Embedded is not visible so host will get focus
+    embeddedWindow->setVisible(false);
+    changes = focusResolver.setInputWindows(request.displayId, windows);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ embeddedWindowToken, /*to*/ hostWindowToken);
+
+    // Embedded is now visible so will get focus
+    embeddedWindow->setVisible(true);
+    changes = focusResolver.setInputWindows(request.displayId, windows);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ hostWindowToken, /*to*/ embeddedWindowToken);
+
+    // Remove focusTransferTarget from host. Host will gain focus.
+    hostWindow->editInfo()->focusTransferTarget = nullptr;
+    changes = focusResolver.setInputWindows(request.displayId, windows);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ embeddedWindowToken, /*to*/ hostWindowToken);
+
+    // Set invalid token for focusTransferTarget. Host will remain focus
+    hostWindow->editInfo()->focusTransferTarget = sp<BBinder>::make();
+    changes = focusResolver.setInputWindows(request.displayId, windows);
+    ASSERT_FALSE(changes);
+}
+
+TEST(FocusResolverTest, FocusTransferMultipleInChain) {
     sp<IBinder> hostWindowToken = sp<BBinder>::make();
     std::vector<sp<WindowInfoHandle>> windows;
 
@@ -251,43 +304,60 @@
                                        /*visible=*/true);
     windows.push_back(embeddedWindow);
 
+    sp<IBinder> embeddedWindowToken2 = sp<BBinder>::make();
+    sp<FakeWindowHandle> embeddedWindow2 =
+            sp<FakeWindowHandle>::make("Embedded Window2", embeddedWindowToken2, /*focusable=*/true,
+                                       /*visible=*/true);
+    windows.push_back(embeddedWindow2);
+
     FocusRequest request;
     request.displayId = 42;
     request.token = hostWindowToken;
+
+    hostWindow->editInfo()->focusTransferTarget = embeddedWindowToken;
+    embeddedWindow->editInfo()->focusTransferTarget = embeddedWindowToken2;
+
     FocusResolver focusResolver;
     std::optional<FocusResolver::FocusChanges> changes =
             focusResolver.setFocusedWindow(request, windows);
-    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ hostWindowToken);
-
-    request.focusedToken = hostWindow->getToken();
-    request.token = embeddedWindowToken;
-    changes = focusResolver.setFocusedWindow(request, windows);
-    ASSERT_FOCUS_CHANGE(changes, /*from*/ hostWindowToken, /*to*/ embeddedWindowToken);
-
-    embeddedWindow->setFocusable(false);
-    changes = focusResolver.setInputWindows(request.displayId, windows);
-    // The embedded window is no longer focusable, provide focus back to the original focused
-    // window.
-    ASSERT_FOCUS_CHANGE(changes, /*from*/ embeddedWindowToken, /*to*/ hostWindowToken);
-
-    embeddedWindow->setFocusable(true);
-    changes = focusResolver.setInputWindows(request.displayId, windows);
-    // The embedded window is focusable again, but we it cannot gain focus unless there is another
-    // focus request.
-    ASSERT_FALSE(changes);
-
-    embeddedWindow->setVisible(false);
-    changes = focusResolver.setFocusedWindow(request, windows);
-    // If the embedded window is not visible/focusable, then we do not grant it focus and the
-    // request is dropped.
-    ASSERT_FALSE(changes);
-
-    embeddedWindow->setVisible(true);
-    changes = focusResolver.setInputWindows(request.displayId, windows);
-    // If the embedded window becomes visble/focusable, nothing changes since the request has been
-    // dropped.
-    ASSERT_FALSE(changes);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ embeddedWindowToken2);
 }
+
+TEST(FocusResolverTest, FocusTransferTargetCycle) {
+    sp<IBinder> hostWindowToken = sp<BBinder>::make();
+    std::vector<sp<WindowInfoHandle>> windows;
+
+    sp<FakeWindowHandle> hostWindow =
+            sp<FakeWindowHandle>::make("Host Window", hostWindowToken, /*focusable=*/true,
+                                       /*visible=*/true);
+    windows.push_back(hostWindow);
+    sp<IBinder> embeddedWindowToken = sp<BBinder>::make();
+    sp<FakeWindowHandle> embeddedWindow =
+            sp<FakeWindowHandle>::make("Embedded Window", embeddedWindowToken, /*focusable=*/true,
+                                       /*visible=*/true);
+    windows.push_back(embeddedWindow);
+
+    sp<IBinder> embeddedWindowToken2 = sp<BBinder>::make();
+    sp<FakeWindowHandle> embeddedWindow2 =
+            sp<FakeWindowHandle>::make("Embedded Window2", embeddedWindowToken2, /*focusable=*/true,
+                                       /*visible=*/true);
+    windows.push_back(embeddedWindow2);
+
+    FocusRequest request;
+    request.displayId = 42;
+    request.token = hostWindowToken;
+
+    hostWindow->editInfo()->focusTransferTarget = embeddedWindowToken;
+    embeddedWindow->editInfo()->focusTransferTarget = embeddedWindowToken2;
+    embeddedWindow2->editInfo()->focusTransferTarget = hostWindowToken;
+
+    FocusResolver focusResolver;
+    std::optional<FocusResolver::FocusChanges> changes =
+            focusResolver.setFocusedWindow(request, windows);
+    // Cycle will be detected and stop right before trying to transfer token to host again.
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ embeddedWindowToken2);
+}
+
 TEST(FocusResolverTest, FocusRequestsAreClearedWhenWindowIsRemoved) {
     sp<IBinder> windowToken = sp<BBinder>::make();
     std::vector<sp<WindowInfoHandle>> windows;
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index 9c624ba..a723636 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -93,7 +93,7 @@
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
                       WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithButtonState(0),
+                      WithToolType(ToolType::FINGER), WithButtonState(0),
                       WithPressure(0.0f)));
 
     ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
@@ -111,7 +111,7 @@
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
                       WithCoords(POINTER_X + 10, POINTER_Y + 5), WithRelativeMotion(10, 5),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithButtonState(0),
+                      WithToolType(ToolType::FINGER), WithButtonState(0),
                       WithPressure(0.0f)));
 
     ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X + 10, POINTER_Y + 5));
@@ -133,14 +133,14 @@
                       WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
                                       AMOTION_EVENT_BUTTON_SECONDARY),
                       WithCoords(POINTER_X, POINTER_Y),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(ToolType::FINGER)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
                       WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
                       WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
                       WithCoords(POINTER_X, POINTER_Y),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(ToolType::FINGER)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
@@ -148,7 +148,7 @@
                       WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
                                       AMOTION_EVENT_BUTTON_SECONDARY),
                       WithCoords(POINTER_X, POINTER_Y),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(ToolType::FINGER)));
 
     // Then release the left button
     Gesture leftUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
@@ -162,7 +162,7 @@
                       WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
                       WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY),
                       WithCoords(POINTER_X, POINTER_Y),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(ToolType::FINGER)));
 
     // Finally release the right button
     Gesture rightUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
@@ -175,12 +175,12 @@
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
                       WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0),
                       WithCoords(POINTER_X, POINTER_Y),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(ToolType::FINGER)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
                       WithCoords(POINTER_X, POINTER_Y),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(ToolType::FINGER)));
 }
 
 TEST_F(GestureConverterTest, DragWithButton) {
@@ -198,14 +198,14 @@
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
                       WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
                       WithCoords(POINTER_X, POINTER_Y),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(ToolType::FINGER)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
                       WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
                       WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
                       WithCoords(POINTER_X, POINTER_Y),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(ToolType::FINGER)));
 
     // Move
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
@@ -215,7 +215,7 @@
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
                       WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER),
+                      WithToolType(ToolType::FINGER),
                       WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f)));
 
     ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
@@ -231,12 +231,12 @@
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
                       WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
                       WithCoords(POINTER_X - 5, POINTER_Y + 10),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(ToolType::FINGER)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
                       WithCoords(POINTER_X - 5, POINTER_Y + 10),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(ToolType::FINGER)));
 }
 
 TEST_F(GestureConverterTest, Scroll) {
@@ -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(ToolType::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(ToolType::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(ToolType::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(ToolType::FINGER),
+                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE)));
 }
 
 TEST_F(GestureConverterTest, Scroll_Rotated) {
@@ -297,14 +301,14 @@
                 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(ToolType::FINGER), WithDownTime(downTime)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
                       WithCoords(POINTER_X - 10, POINTER_Y),
                       WithGestureScrollDistance(0, 10, EPSILON),
                       WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(ToolType::FINGER)));
 
     Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
     args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
@@ -314,7 +318,7 @@
                       WithCoords(POINTER_X - 15, POINTER_Y),
                       WithGestureScrollDistance(0, 5, EPSILON),
                       WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(ToolType::FINGER)));
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
                          GESTURES_FLING_START);
@@ -325,7 +329,7 @@
                       WithCoords(POINTER_X - 15, POINTER_Y),
                       WithGestureScrollDistance(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(ToolType::FINGER)));
 }
 
 TEST_F(GestureConverterTest, Scroll_ClearsClassificationAndOffsetsAfterGesture) {
@@ -389,7 +393,7 @@
     ASSERT_THAT(arg,
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithPointerCount(1u), WithToolType(ToolType::FINGER)));
     PointerCoords finger0Start = arg.pointerCoords[0];
     args.pop_front();
     arg = std::get<NotifyMotionArgs>(args.front());
@@ -398,7 +402,7 @@
                                        1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithGestureOffset(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithPointerCount(2u), WithToolType(ToolType::FINGER)));
     PointerCoords finger1Start = arg.pointerCoords[1];
     args.pop_front();
     arg = std::get<NotifyMotionArgs>(args.front());
@@ -407,7 +411,7 @@
                                        2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithGestureOffset(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER)));
     PointerCoords finger2Start = arg.pointerCoords[2];
     args.pop_front();
 
@@ -416,7 +420,7 @@
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
                       WithGestureOffset(0, -0.01, EPSILON),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER)));
     EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX());
     EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX());
     EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX());
@@ -433,7 +437,7 @@
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
                       WithGestureOffset(0, -0.005, EPSILON),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER)));
     EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX());
     EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX());
     EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX());
@@ -449,19 +453,19 @@
                                        2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithGestureOffset(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
                                        1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithGestureOffset(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithPointerCount(2u), WithToolType(ToolType::FINGER)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithPointerCount(1u), WithToolType(ToolType::FINGER)));
 }
 
 TEST_F(GestureConverterTest, ThreeFingerSwipe_Rotated) {
@@ -556,7 +560,7 @@
     ASSERT_THAT(arg,
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithPointerCount(1u), WithToolType(ToolType::FINGER)));
     PointerCoords finger0Start = arg.pointerCoords[0];
     args.pop_front();
     arg = std::get<NotifyMotionArgs>(args.front());
@@ -565,7 +569,7 @@
                                        1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithGestureOffset(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithPointerCount(2u), WithToolType(ToolType::FINGER)));
     PointerCoords finger1Start = arg.pointerCoords[1];
     args.pop_front();
     arg = std::get<NotifyMotionArgs>(args.front());
@@ -574,7 +578,7 @@
                                        2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithGestureOffset(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER)));
     PointerCoords finger2Start = arg.pointerCoords[2];
     args.pop_front();
     arg = std::get<NotifyMotionArgs>(args.front());
@@ -583,7 +587,7 @@
                                        3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithGestureOffset(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(4u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithPointerCount(4u), WithToolType(ToolType::FINGER)));
     PointerCoords finger3Start = arg.pointerCoords[3];
     args.pop_front();
 
@@ -592,7 +596,7 @@
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
                       WithGestureOffset(0.01, 0, EPSILON),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(4u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithPointerCount(4u), WithToolType(ToolType::FINGER)));
     EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 10);
     EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 10);
     EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 10);
@@ -611,7 +615,7 @@
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
                       WithGestureOffset(0.005, 0, EPSILON),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(4u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithPointerCount(4u), WithToolType(ToolType::FINGER)));
     EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 15);
     EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 15);
     EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 15);
@@ -629,26 +633,26 @@
                                        3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithGestureOffset(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(4u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithPointerCount(4u), WithToolType(ToolType::FINGER)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
                                        2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithGestureOffset(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
                                        1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithGestureOffset(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithPointerCount(2u), WithToolType(ToolType::FINGER)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithPointerCount(1u), WithToolType(ToolType::FINGER)));
 }
 
 TEST_F(GestureConverterTest, Pinch_Inwards) {
@@ -664,7 +668,7 @@
                       WithMotionClassification(MotionClassification::PINCH),
                       WithGesturePinchScaleFactor(1.0f, EPSILON),
                       WithCoords(POINTER_X - 100, POINTER_Y), WithPointerCount(1u),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(ToolType::FINGER)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
@@ -672,7 +676,7 @@
                       WithMotionClassification(MotionClassification::PINCH),
                       WithGesturePinchScaleFactor(1.0f, EPSILON),
                       WithPointerCoords(1, POINTER_X + 100, POINTER_Y), WithPointerCount(2u),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(ToolType::FINGER)));
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /* dz= */ 0.8, GESTURES_ZOOM_UPDATE);
@@ -684,7 +688,7 @@
                       WithGesturePinchScaleFactor(0.8f, EPSILON),
                       WithPointerCoords(0, POINTER_X - 80, POINTER_Y),
                       WithPointerCoords(1, POINTER_X + 80, POINTER_Y), WithPointerCount(2u),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(ToolType::FINGER)));
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                        GESTURES_ZOOM_END);
@@ -695,13 +699,13 @@
                                        1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithMotionClassification(MotionClassification::PINCH),
                       WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(ToolType::FINGER)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
                       WithMotionClassification(MotionClassification::PINCH),
                       WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(ToolType::FINGER)));
 }
 
 TEST_F(GestureConverterTest, Pinch_Outwards) {
@@ -717,7 +721,7 @@
                       WithMotionClassification(MotionClassification::PINCH),
                       WithGesturePinchScaleFactor(1.0f, EPSILON),
                       WithCoords(POINTER_X - 100, POINTER_Y), WithPointerCount(1u),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(ToolType::FINGER)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
@@ -725,7 +729,7 @@
                       WithMotionClassification(MotionClassification::PINCH),
                       WithGesturePinchScaleFactor(1.0f, EPSILON),
                       WithPointerCoords(1, POINTER_X + 100, POINTER_Y), WithPointerCount(2u),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(ToolType::FINGER)));
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /* dz= */ 1.2, GESTURES_ZOOM_UPDATE);
@@ -737,7 +741,7 @@
                       WithGesturePinchScaleFactor(1.2f, EPSILON),
                       WithPointerCoords(0, POINTER_X - 120, POINTER_Y),
                       WithPointerCoords(1, POINTER_X + 120, POINTER_Y), WithPointerCount(2u),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(ToolType::FINGER)));
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                        GESTURES_ZOOM_END);
@@ -748,13 +752,13 @@
                                        1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithMotionClassification(MotionClassification::PINCH),
                       WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(ToolType::FINGER)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
                       WithMotionClassification(MotionClassification::PINCH),
                       WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+                      WithToolType(ToolType::FINGER)));
 }
 
 TEST_F(GestureConverterTest, Pinch_ClearsClassificationAndScaleFactorAfterGesture) {
@@ -781,4 +785,124 @@
                       WithGesturePinchScaleFactor(0, EPSILON)));
 }
 
+TEST_F(GestureConverterTest, ResetWithButtonPressed) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+    Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                        /*down=*/GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
+                        /*up=*/GESTURES_BUTTON_NONE, /*is_tap=*/false);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
+
+    std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
+    ASSERT_EQ(3u, args.size());
+
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY),
+                      WithCoords(POINTER_X, POINTER_Y),
+                      WithToolType(ToolType::FINGER)));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0),
+                      WithCoords(POINTER_X, POINTER_Y),
+                      WithToolType(ToolType::FINGER)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
+                      WithCoords(POINTER_X, POINTER_Y),
+                      WithToolType(ToolType::FINGER)));
+}
+
+TEST_F(GestureConverterTest, ResetDuringScroll) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+    std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                      WithCoords(POINTER_X, POINTER_Y - 10),
+                      WithGestureScrollDistance(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                      WithToolType(ToolType::FINGER),
+                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE)));
+}
+
+TEST_F(GestureConverterTest, ResetDuringThreeFingerSwipe) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
+                         /*dy=*/10);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+    std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
+    ASSERT_EQ(3u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER)));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(2u), WithToolType(ToolType::FINGER)));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(1u), WithToolType(ToolType::FINGER)));
+}
+
+TEST_F(GestureConverterTest, ResetDuringPinch) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+                         GESTURES_ZOOM_START);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+    std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
+                      WithToolType(ToolType::FINGER)));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
+                      WithToolType(ToolType::FINGER)));
+}
+
+TEST_F(GestureConverterTest, FlingTapDown) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+    Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                           /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapDownGesture);
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
+                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f)));
+
+    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X, POINTER_Y));
+    ASSERT_TRUE(mFakePointerController->isPointerShown());
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/HardwareStateConverter_test.cpp b/services/inputflinger/tests/HardwareStateConverter_test.cpp
index 36b9bab..5bea2ba 100644
--- a/services/inputflinger/tests/HardwareStateConverter_test.cpp
+++ b/services/inputflinger/tests/HardwareStateConverter_test.cpp
@@ -13,53 +13,58 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include <gestures/HardwareStateConverter.h>
+
+#include <memory>
 
 #include <EventHub.h>
-#include <gestures/HardwareStateConverter.h>
 #include <gtest/gtest.h>
 #include <linux/input-event-codes.h>
+#include <utils/StrongPointer.h>
 
 #include "FakeEventHub.h"
 #include "FakeInputReaderPolicy.h"
 #include "InstrumentedInputReader.h"
+#include "MultiTouchMotionAccumulator.h"
 #include "TestConstants.h"
 #include "TestInputListener.h"
 
 namespace android {
 
 class HardwareStateConverterTest : public testing::Test {
+public:
+    HardwareStateConverterTest()
+          : mFakeEventHub(std::make_shared<FakeEventHub>()),
+            mFakePolicy(sp<FakeInputReaderPolicy>::make()),
+            mReader(mFakeEventHub, mFakePolicy, mFakeListener),
+            mDevice(newDevice()),
+            mDeviceContext(*mDevice, EVENTHUB_ID) {
+        const size_t slotCount = 8;
+        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_SLOT, 0, slotCount - 1, 0, 0, 0);
+        mAccumulator.configure(mDeviceContext, slotCount, /*usingSlotsProtocol=*/true);
+        mConverter = std::make_unique<HardwareStateConverter>(mDeviceContext, mAccumulator);
+    }
+
 protected:
     static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
     static constexpr int32_t EVENTHUB_ID = 1;
 
-    void SetUp() {
-        mFakeEventHub = std::make_unique<FakeEventHub>();
-        mFakePolicy = sp<FakeInputReaderPolicy>::make();
-        mFakeListener = std::make_unique<TestInputListener>();
-        mReader = std::make_unique<InstrumentedInputReader>(mFakeEventHub, mFakePolicy,
-                                                            *mFakeListener);
-        mDevice = newDevice();
-
-        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_SLOT, 0, 7, 0, 0, 0);
-    }
-
     std::shared_ptr<InputDevice> newDevice() {
         InputDeviceIdentifier identifier;
         identifier.name = "device";
         identifier.location = "USB1";
         identifier.bus = 0;
         std::shared_ptr<InputDevice> device =
-                std::make_shared<InputDevice>(mReader->getContext(), DEVICE_ID, /* generation= */ 2,
+                std::make_shared<InputDevice>(mReader.getContext(), DEVICE_ID, /*generation=*/2,
                                               identifier);
-        mReader->pushNextDevice(device);
+        mReader.pushNextDevice(device);
         mFakeEventHub->addDevice(EVENTHUB_ID, identifier.name, InputDeviceClass::TOUCHPAD,
                                  identifier.bus);
-        mReader->loopOnce();
+        mReader.loopOnce();
         return device;
     }
 
-    void processAxis(HardwareStateConverter& conv, nsecs_t when, int32_t type, int32_t code,
-                     int32_t value) {
+    void processAxis(nsecs_t when, int32_t type, int32_t code, int32_t value) {
         RawEvent event;
         event.when = when;
         event.readTime = READ_TIME;
@@ -67,12 +72,11 @@
         event.type = type;
         event.code = code;
         event.value = value;
-        std::optional<SelfContainedHardwareState> schs = conv.processRawEvent(&event);
+        std::optional<SelfContainedHardwareState> schs = mConverter->processRawEvent(&event);
         EXPECT_FALSE(schs.has_value());
     }
 
-    std::optional<SelfContainedHardwareState> processSync(HardwareStateConverter& conv,
-                                                          nsecs_t when) {
+    std::optional<SelfContainedHardwareState> processSync(nsecs_t when) {
         RawEvent event;
         event.when = when;
         event.readTime = READ_TIME;
@@ -80,37 +84,38 @@
         event.type = EV_SYN;
         event.code = SYN_REPORT;
         event.value = 0;
-        return conv.processRawEvent(&event);
+        return mConverter->processRawEvent(&event);
     }
 
     std::shared_ptr<FakeEventHub> mFakeEventHub;
     sp<FakeInputReaderPolicy> mFakePolicy;
-    std::unique_ptr<TestInputListener> mFakeListener;
-    std::unique_ptr<InstrumentedInputReader> mReader;
+    TestInputListener mFakeListener;
+    InstrumentedInputReader mReader;
     std::shared_ptr<InputDevice> mDevice;
+    InputDeviceContext mDeviceContext;
+    MultiTouchMotionAccumulator mAccumulator;
+    std::unique_ptr<HardwareStateConverter> mConverter;
 };
 
 TEST_F(HardwareStateConverterTest, OneFinger) {
     const nsecs_t time = 1500000000;
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    HardwareStateConverter conv(deviceContext);
 
-    processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 0);
-    processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 123);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 50);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 100);
-    processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MAJOR, 5);
-    processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MINOR, 4);
-    processAxis(conv, time, EV_ABS, ABS_MT_PRESSURE, 42);
-    processAxis(conv, time, EV_ABS, ABS_MT_ORIENTATION, 2);
+    processAxis(time, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(time, EV_ABS, ABS_MT_TRACKING_ID, 123);
+    processAxis(time, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(time, EV_ABS, ABS_MT_POSITION_Y, 100);
+    processAxis(time, EV_ABS, ABS_MT_TOUCH_MAJOR, 5);
+    processAxis(time, EV_ABS, ABS_MT_TOUCH_MINOR, 4);
+    processAxis(time, EV_ABS, ABS_MT_PRESSURE, 42);
+    processAxis(time, EV_ABS, ABS_MT_ORIENTATION, 2);
 
-    processAxis(conv, time, EV_ABS, ABS_X, 50);
-    processAxis(conv, time, EV_ABS, ABS_Y, 100);
-    processAxis(conv, time, EV_ABS, ABS_PRESSURE, 42);
+    processAxis(time, EV_ABS, ABS_X, 50);
+    processAxis(time, EV_ABS, ABS_Y, 100);
+    processAxis(time, EV_ABS, ABS_PRESSURE, 42);
 
-    processAxis(conv, time, EV_KEY, BTN_TOUCH, 1);
-    processAxis(conv, time, EV_KEY, BTN_TOOL_FINGER, 1);
-    std::optional<SelfContainedHardwareState> schs = processSync(conv, time);
+    processAxis(time, EV_KEY, BTN_TOUCH, 1);
+    processAxis(time, EV_KEY, BTN_TOOL_FINGER, 1);
+    std::optional<SelfContainedHardwareState> schs = processSync(time);
 
     ASSERT_TRUE(schs.has_value());
     const HardwareState& state = schs->state;
@@ -138,35 +143,31 @@
 }
 
 TEST_F(HardwareStateConverterTest, TwoFingers) {
-    const nsecs_t time = ARBITRARY_TIME;
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    HardwareStateConverter conv(deviceContext);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TRACKING_ID, 123);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 100);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOUCH_MAJOR, 5);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOUCH_MINOR, 4);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_PRESSURE, 42);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_ORIENTATION, 2);
 
-    processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 0);
-    processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 123);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 50);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 100);
-    processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MAJOR, 5);
-    processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MINOR, 4);
-    processAxis(conv, time, EV_ABS, ABS_MT_PRESSURE, 42);
-    processAxis(conv, time, EV_ABS, ABS_MT_ORIENTATION, 2);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_SLOT, 1);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TRACKING_ID, 456);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, -20);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 40);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOUCH_MAJOR, 8);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOUCH_MINOR, 7);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_PRESSURE, 21);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_ORIENTATION, 1);
 
-    processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 1);
-    processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 456);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, -20);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 40);
-    processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MAJOR, 8);
-    processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MINOR, 7);
-    processAxis(conv, time, EV_ABS, ABS_MT_PRESSURE, 21);
-    processAxis(conv, time, EV_ABS, ABS_MT_ORIENTATION, 1);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_X, 50);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_Y, 100);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_PRESSURE, 42);
 
-    processAxis(conv, time, EV_ABS, ABS_X, 50);
-    processAxis(conv, time, EV_ABS, ABS_Y, 100);
-    processAxis(conv, time, EV_ABS, ABS_PRESSURE, 42);
-
-    processAxis(conv, time, EV_KEY, BTN_TOUCH, 1);
-    processAxis(conv, time, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
-    std::optional<SelfContainedHardwareState> schs = processSync(conv, time);
+    processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOUCH, 1);
+    processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
+    std::optional<SelfContainedHardwareState> schs = processSync(ARBITRARY_TIME);
 
     ASSERT_TRUE(schs.has_value());
     ASSERT_EQ(2, schs->state.finger_cnt);
@@ -192,60 +193,58 @@
 }
 
 TEST_F(HardwareStateConverterTest, OnePalm) {
-    const nsecs_t time = ARBITRARY_TIME;
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    HardwareStateConverter conv(deviceContext);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TRACKING_ID, 123);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 100);
 
-    processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 0);
-    processAxis(conv, time, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM);
-    processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 123);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 50);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 100);
-
-    processAxis(conv, time, EV_KEY, BTN_TOUCH, 1);
-    std::optional<SelfContainedHardwareState> schs = processSync(conv, time);
+    processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOUCH, 1);
+    processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOOL_FINGER, 1);
+    std::optional<SelfContainedHardwareState> schs = processSync(ARBITRARY_TIME);
     ASSERT_TRUE(schs.has_value());
+    EXPECT_EQ(0, schs->state.touch_cnt);
     EXPECT_EQ(0, schs->state.finger_cnt);
 }
 
 TEST_F(HardwareStateConverterTest, OneFingerTurningIntoAPalm) {
-    const nsecs_t time = ARBITRARY_TIME;
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    HardwareStateConverter conv(deviceContext);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TRACKING_ID, 123);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 100);
 
-    processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 0);
-    processAxis(conv, time, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER);
-    processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 123);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 50);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 100);
+    processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOUCH, 1);
+    processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOOL_FINGER, 1);
 
-    processAxis(conv, time, EV_KEY, BTN_TOUCH, 1);
-
-    std::optional<SelfContainedHardwareState> schs = processSync(conv, time);
+    std::optional<SelfContainedHardwareState> schs = processSync(ARBITRARY_TIME);
     ASSERT_TRUE(schs.has_value());
+    EXPECT_EQ(1, schs->state.touch_cnt);
     EXPECT_EQ(1, schs->state.finger_cnt);
 
-    processAxis(conv, time, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 51);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 99);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 51);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 99);
 
-    schs = processSync(conv, time);
+    schs = processSync(ARBITRARY_TIME);
     ASSERT_TRUE(schs.has_value());
-    ASSERT_EQ(1, schs->state.finger_cnt);
-    EXPECT_EQ(-1, schs->state.fingers[0].tracking_id);
+    EXPECT_EQ(0, schs->state.touch_cnt);
+    ASSERT_EQ(0, schs->state.finger_cnt);
 
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 53);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 97);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 53);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 97);
 
-    schs = processSync(conv, time);
+    schs = processSync(ARBITRARY_TIME);
     ASSERT_TRUE(schs.has_value());
+    EXPECT_EQ(0, schs->state.touch_cnt);
     EXPECT_EQ(0, schs->state.finger_cnt);
 
-    processAxis(conv, time, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 55);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 95);
-    schs = processSync(conv, time);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 55);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 95);
+    schs = processSync(ARBITRARY_TIME);
     ASSERT_TRUE(schs.has_value());
+    EXPECT_EQ(1, schs->state.touch_cnt);
     ASSERT_EQ(1, schs->state.finger_cnt);
     const FingerState& newFinger = schs->state.fingers[0];
     EXPECT_EQ(123, newFinger.tracking_id);
@@ -254,25 +253,16 @@
 }
 
 TEST_F(HardwareStateConverterTest, ButtonPressed) {
-    const nsecs_t time = ARBITRARY_TIME;
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    HardwareStateConverter conv(deviceContext);
-
-    processAxis(conv, time, EV_KEY, BTN_LEFT, 1);
-    std::optional<SelfContainedHardwareState> schs = processSync(conv, time);
+    processAxis(ARBITRARY_TIME, EV_KEY, BTN_LEFT, 1);
+    std::optional<SelfContainedHardwareState> schs = processSync(ARBITRARY_TIME);
 
     ASSERT_TRUE(schs.has_value());
     EXPECT_EQ(GESTURES_BUTTON_LEFT, schs->state.buttons_down);
 }
 
 TEST_F(HardwareStateConverterTest, MscTimestamp) {
-    const nsecs_t time = ARBITRARY_TIME;
-    mFakeEventHub->setMscEvent(EVENTHUB_ID, MSC_TIMESTAMP);
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    HardwareStateConverter conv(deviceContext);
-
-    processAxis(conv, time, EV_MSC, MSC_TIMESTAMP, 1200000);
-    std::optional<SelfContainedHardwareState> schs = processSync(conv, time);
+    processAxis(ARBITRARY_TIME, EV_MSC, MSC_TIMESTAMP, 1200000);
+    std::optional<SelfContainedHardwareState> schs = processSync(ARBITRARY_TIME);
 
     ASSERT_TRUE(schs.has_value());
     EXPECT_NEAR(1.2, schs->state.msc_timestamp, EPSILON);
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index e4ba241..3f2658a 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -62,6 +62,7 @@
 static constexpr int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE;
 static constexpr int32_t ACTION_UP = AMOTION_EVENT_ACTION_UP;
 static constexpr int32_t ACTION_HOVER_ENTER = AMOTION_EVENT_ACTION_HOVER_ENTER;
+static constexpr int32_t ACTION_HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE;
 static constexpr int32_t ACTION_HOVER_EXIT = AMOTION_EVENT_ACTION_HOVER_EXIT;
 static constexpr int32_t ACTION_OUTSIDE = AMOTION_EVENT_ACTION_OUTSIDE;
 static constexpr int32_t ACTION_CANCEL = AMOTION_EVENT_ACTION_CANCEL;
@@ -85,6 +86,8 @@
         AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 static constexpr int32_t POINTER_1_UP =
         AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+static constexpr int32_t POINTER_2_UP =
+        AMOTION_EVENT_ACTION_POINTER_UP | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 
 // The default pid and uid for windows created on the primary display by the test.
 static constexpr int32_t WINDOW_PID = 999;
@@ -205,15 +208,13 @@
 
     using AnrResult = std::pair<sp<IBinder>, int32_t /*pid*/>;
 
-protected:
-    virtual ~FakeInputDispatcherPolicy() {}
-
 public:
-    FakeInputDispatcherPolicy() {}
+    FakeInputDispatcherPolicy() = default;
+    virtual ~FakeInputDispatcherPolicy() = default;
 
     void assertFilterInputEventWasCalled(const NotifyKeyArgs& args) {
         assertFilterInputEventWasCalledInternal([&args](const InputEvent& event) {
-            ASSERT_EQ(event.getType(), AINPUT_EVENT_TYPE_KEY);
+            ASSERT_EQ(event.getType(), InputEventType::KEY);
             EXPECT_EQ(event.getDisplayId(), args.displayId);
 
             const auto& keyEvent = static_cast<const KeyEvent&>(event);
@@ -224,16 +225,16 @@
 
     void assertFilterInputEventWasCalled(const NotifyMotionArgs& args, vec2 point) {
         assertFilterInputEventWasCalledInternal([&](const InputEvent& event) {
-            ASSERT_EQ(event.getType(), AINPUT_EVENT_TYPE_MOTION);
+            ASSERT_EQ(event.getType(), InputEventType::MOTION);
             EXPECT_EQ(event.getDisplayId(), args.displayId);
 
             const auto& motionEvent = static_cast<const MotionEvent&>(event);
             EXPECT_EQ(motionEvent.getEventTime(), args.eventTime);
             EXPECT_EQ(motionEvent.getAction(), args.action);
-            EXPECT_EQ(motionEvent.getX(0), point.x);
-            EXPECT_EQ(motionEvent.getY(0), point.y);
-            EXPECT_EQ(motionEvent.getRawX(0), point.x);
-            EXPECT_EQ(motionEvent.getRawY(0), point.y);
+            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);
         });
     }
 
@@ -404,6 +405,16 @@
         mInterceptKeyTimeout = timeout;
     }
 
+    void assertUserActivityPoked() {
+        std::scoped_lock lock(mLock);
+        ASSERT_TRUE(mPokedUserActivity) << "Expected user activity to have been poked";
+    }
+
+    void assertUserActivityNotPoked() {
+        std::scoped_lock lock(mLock);
+        ASSERT_FALSE(mPokedUserActivity) << "Expected user activity not to have been poked";
+    }
+
 private:
     std::mutex mLock;
     std::unique_ptr<InputEvent> mFilteredEvent GUARDED_BY(mLock);
@@ -425,6 +436,7 @@
 
     sp<IBinder> mDropTargetWindowToken GUARDED_BY(mLock);
     bool mNotifyDropWindowWasCalled GUARDED_BY(mLock) = false;
+    bool mPokedUserActivity GUARDED_BY(mLock) = false;
 
     std::chrono::milliseconds mInterceptKeyTimeout = 0ms;
 
@@ -523,30 +535,32 @@
 
     void notifyVibratorState(int32_t deviceId, bool isOn) override {}
 
-    void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) override {
-        *outConfig = mConfig;
-    }
+    InputDispatcherConfiguration getDispatcherConfiguration() override { return mConfig; }
 
-    bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) override {
+    bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override {
         std::scoped_lock lock(mLock);
-        switch (inputEvent->getType()) {
-            case AINPUT_EVENT_TYPE_KEY: {
-                const KeyEvent* keyEvent = static_cast<const KeyEvent*>(inputEvent);
-                mFilteredEvent = std::make_unique<KeyEvent>(*keyEvent);
+        switch (inputEvent.getType()) {
+            case InputEventType::KEY: {
+                const KeyEvent& keyEvent = static_cast<const KeyEvent&>(inputEvent);
+                mFilteredEvent = std::make_unique<KeyEvent>(keyEvent);
                 break;
             }
 
-            case AINPUT_EVENT_TYPE_MOTION: {
-                const MotionEvent* motionEvent = static_cast<const MotionEvent*>(inputEvent);
-                mFilteredEvent = std::make_unique<MotionEvent>(*motionEvent);
+            case InputEventType::MOTION: {
+                const MotionEvent& motionEvent = static_cast<const MotionEvent&>(inputEvent);
+                mFilteredEvent = std::make_unique<MotionEvent>(motionEvent);
+                break;
+            }
+            default: {
+                ADD_FAILURE() << "Should only filter keys or motions";
                 break;
             }
         }
         return true;
     }
 
-    void interceptKeyBeforeQueueing(const KeyEvent* inputEvent, uint32_t&) override {
-        if (inputEvent->getAction() == AKEY_EVENT_ACTION_UP) {
+    void interceptKeyBeforeQueueing(const KeyEvent& inputEvent, uint32_t&) override {
+        if (inputEvent.getAction() == AKEY_EVENT_ACTION_UP) {
             // Clear intercept state when we handled the event.
             mInterceptKeyTimeout = 0ms;
         }
@@ -554,15 +568,16 @@
 
     void interceptMotionBeforeQueueing(int32_t, nsecs_t, uint32_t&) override {}
 
-    nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent*, uint32_t) override {
+    nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent&, uint32_t) override {
         nsecs_t delay = std::chrono::nanoseconds(mInterceptKeyTimeout).count();
         // Clear intercept state so we could dispatch the event in next wake.
         mInterceptKeyTimeout = 0ms;
         return delay;
     }
 
-    bool dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent*, uint32_t, KeyEvent*) override {
-        return false;
+    std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent&,
+                                                 uint32_t) override {
+        return {};
     }
 
     void notifySwitch(nsecs_t when, uint32_t switchValues, uint32_t switchMask,
@@ -574,7 +589,10 @@
         mLastNotifySwitch = NotifySwitchArgs(/*id=*/1, when, policyFlags, switchValues, switchMask);
     }
 
-    void pokeUserActivity(nsecs_t, int32_t, int32_t) override {}
+    void pokeUserActivity(nsecs_t, int32_t, int32_t) override {
+        std::scoped_lock lock(mLock);
+        mPokedUserActivity = true;
+    }
 
     void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override {
         std::scoped_lock lock(mLock);
@@ -606,12 +624,12 @@
 
 class InputDispatcherTest : public testing::Test {
 protected:
-    sp<FakeInputDispatcherPolicy> mFakePolicy;
+    std::unique_ptr<FakeInputDispatcherPolicy> mFakePolicy;
     std::unique_ptr<InputDispatcher> mDispatcher;
 
     void SetUp() override {
-        mFakePolicy = sp<FakeInputDispatcherPolicy>::make();
-        mDispatcher = std::make_unique<InputDispatcher>(mFakePolicy, STALE_EVENT_TIMEOUT);
+        mFakePolicy = std::make_unique<FakeInputDispatcherPolicy>();
+        mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy, STALE_EVENT_TIMEOUT);
         mDispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
         // Start InputDispatcher thread
         ASSERT_EQ(OK, mDispatcher->start());
@@ -619,7 +637,7 @@
 
     void TearDown() override {
         ASSERT_EQ(OK, mDispatcher->stop());
-        mFakePolicy.clear();
+        mFakePolicy.reset();
         mDispatcher.reset();
     }
 
@@ -637,14 +655,10 @@
         }
     }
 
-    void setFocusedWindow(const sp<WindowInfoHandle>& window,
-                          const sp<WindowInfoHandle>& focusedWindow = nullptr) {
+    void setFocusedWindow(const sp<WindowInfoHandle>& window) {
         FocusRequest request;
         request.token = window->getToken();
         request.windowName = window->getName();
-        if (focusedWindow) {
-            request.focusedToken = focusedWindow->getToken();
-        }
         request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
         request.displayId = window->getInfo()->displayId;
         mDispatcher->setFocusedWindow(request);
@@ -818,8 +832,7 @@
 
 TEST_F(InputDispatcherTest, NotifyConfigurationChanged_CallsPolicy) {
     constexpr nsecs_t eventTime = 20;
-    NotifyConfigurationChangedArgs args(/*id=*/10, eventTime);
-    mDispatcher->notifyConfigurationChanged(&args);
+    mDispatcher->notifyConfigurationChanged({/*id=*/10, eventTime});
     ASSERT_TRUE(mDispatcher->waitForIdle());
 
     mFakePolicy->assertNotifyConfigurationChangedWasCalled(eventTime);
@@ -828,7 +841,7 @@
 TEST_F(InputDispatcherTest, NotifySwitch_CallsPolicy) {
     NotifySwitchArgs args(/*id=*/10, /*eventTime=*/20, /*policyFlags=*/0, /*switchValues=*/1,
                           /*switchMask=*/2);
-    mDispatcher->notifySwitch(&args);
+    mDispatcher->notifySwitch(args);
 
     // InputDispatcher adds POLICY_FLAG_TRUSTED because the event went through InputListener
     args.policyFlags |= POLICY_FLAG_TRUSTED;
@@ -928,7 +941,7 @@
         ASSERT_EQ(OK, status);
     }
 
-    void consumeEvent(int32_t expectedEventType, int32_t expectedAction,
+    void consumeEvent(InputEventType expectedEventType, int32_t expectedAction,
                       std::optional<int32_t> expectedDisplayId,
                       std::optional<int32_t> expectedFlags) {
         InputEvent* event = consume();
@@ -936,15 +949,15 @@
         ASSERT_NE(nullptr, event) << mName.c_str()
                                   << ": consumer should have returned non-NULL event.";
         ASSERT_EQ(expectedEventType, event->getType())
-                << mName.c_str() << " expected " << inputEventTypeToString(expectedEventType)
-                << " event, got " << inputEventTypeToString(event->getType()) << " event";
+                << mName.c_str() << " expected " << ftl::enum_string(expectedEventType)
+                << " event, got " << *event;
 
         if (expectedDisplayId.has_value()) {
             EXPECT_EQ(expectedDisplayId, event->getDisplayId());
         }
 
         switch (expectedEventType) {
-            case AINPUT_EVENT_TYPE_KEY: {
+            case InputEventType::KEY: {
                 const KeyEvent& keyEvent = static_cast<const KeyEvent&>(*event);
                 EXPECT_EQ(expectedAction, keyEvent.getAction());
                 if (expectedFlags.has_value()) {
@@ -952,7 +965,7 @@
                 }
                 break;
             }
-            case AINPUT_EVENT_TYPE_MOTION: {
+            case InputEventType::MOTION: {
                 const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event);
                 assertMotionAction(expectedAction, motionEvent.getAction());
 
@@ -961,21 +974,18 @@
                 }
                 break;
             }
-            case AINPUT_EVENT_TYPE_FOCUS: {
+            case InputEventType::FOCUS: {
                 FAIL() << "Use 'consumeFocusEvent' for FOCUS events";
             }
-            case AINPUT_EVENT_TYPE_CAPTURE: {
+            case InputEventType::CAPTURE: {
                 FAIL() << "Use 'consumeCaptureEvent' for CAPTURE events";
             }
-            case AINPUT_EVENT_TYPE_TOUCH_MODE: {
+            case InputEventType::TOUCH_MODE: {
                 FAIL() << "Use 'consumeTouchModeEvent' for TOUCH_MODE events";
             }
-            case AINPUT_EVENT_TYPE_DRAG: {
+            case InputEventType::DRAG: {
                 FAIL() << "Use 'consumeDragEvent' for DRAG events";
             }
-            default: {
-                FAIL() << mName.c_str() << ": invalid event type: " << expectedEventType;
-            }
         }
     }
 
@@ -987,9 +997,8 @@
             return nullptr;
         }
 
-        if (event->getType() != AINPUT_EVENT_TYPE_MOTION) {
-            ADD_FAILURE() << mName << " expected a MotionEvent, got "
-                          << inputEventTypeToString(event->getType()) << " event";
+        if (event->getType() != InputEventType::MOTION) {
+            ADD_FAILURE() << mName << " expected a MotionEvent, got " << *event;
             return nullptr;
         }
         return static_cast<MotionEvent*>(event);
@@ -1005,9 +1014,8 @@
         InputEvent* event = consume();
         ASSERT_NE(nullptr, event) << mName.c_str()
                                   << ": consumer should have returned non-NULL event.";
-        ASSERT_EQ(AINPUT_EVENT_TYPE_FOCUS, event->getType())
-                << "Got " << inputEventTypeToString(event->getType())
-                << " event instead of FOCUS event";
+        ASSERT_EQ(InputEventType::FOCUS, event->getType())
+                << "Instead of FocusEvent, got " << *event;
 
         ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId())
                 << mName.c_str() << ": event displayId should always be NONE.";
@@ -1020,9 +1028,8 @@
         const InputEvent* event = consume();
         ASSERT_NE(nullptr, event) << mName.c_str()
                                   << ": consumer should have returned non-NULL event.";
-        ASSERT_EQ(AINPUT_EVENT_TYPE_CAPTURE, event->getType())
-                << "Got " << inputEventTypeToString(event->getType())
-                << " event instead of CAPTURE event";
+        ASSERT_EQ(InputEventType::CAPTURE, event->getType())
+                << "Instead of CaptureEvent, got " << *event;
 
         ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId())
                 << mName.c_str() << ": event displayId should always be NONE.";
@@ -1035,9 +1042,7 @@
         const InputEvent* event = consume();
         ASSERT_NE(nullptr, event) << mName.c_str()
                                   << ": consumer should have returned non-NULL event.";
-        ASSERT_EQ(AINPUT_EVENT_TYPE_DRAG, event->getType())
-                << "Got " << inputEventTypeToString(event->getType())
-                << " event instead of DRAG event";
+        ASSERT_EQ(InputEventType::DRAG, event->getType()) << "Instead of DragEvent, got " << *event;
 
         EXPECT_EQ(ADISPLAY_ID_NONE, event->getDisplayId())
                 << mName.c_str() << ": event displayId should always be NONE.";
@@ -1052,9 +1057,8 @@
         const InputEvent* event = consume();
         ASSERT_NE(nullptr, event) << mName.c_str()
                                   << ": consumer should have returned non-NULL event.";
-        ASSERT_EQ(AINPUT_EVENT_TYPE_TOUCH_MODE, event->getType())
-                << "Got " << inputEventTypeToString(event->getType())
-                << " event instead of TOUCH_MODE event";
+        ASSERT_EQ(InputEventType::TOUCH_MODE, event->getType())
+                << "Instead of TouchModeEvent, got " << *event;
 
         ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId())
                 << mName.c_str() << ": event displayId should always be NONE.";
@@ -1067,23 +1071,23 @@
         if (event == nullptr) {
             return;
         }
-        if (event->getType() == AINPUT_EVENT_TYPE_KEY) {
+        if (event->getType() == InputEventType::KEY) {
             KeyEvent& keyEvent = static_cast<KeyEvent&>(*event);
             ADD_FAILURE() << "Received key event "
                           << KeyEvent::actionToString(keyEvent.getAction());
-        } else if (event->getType() == AINPUT_EVENT_TYPE_MOTION) {
+        } else if (event->getType() == InputEventType::MOTION) {
             MotionEvent& motionEvent = static_cast<MotionEvent&>(*event);
             ADD_FAILURE() << "Received motion event "
                           << MotionEvent::actionToString(motionEvent.getAction());
-        } else if (event->getType() == AINPUT_EVENT_TYPE_FOCUS) {
+        } else if (event->getType() == InputEventType::FOCUS) {
             FocusEvent& focusEvent = static_cast<FocusEvent&>(*event);
             ADD_FAILURE() << "Received focus event, hasFocus = "
                           << (focusEvent.getHasFocus() ? "true" : "false");
-        } else if (event->getType() == AINPUT_EVENT_TYPE_CAPTURE) {
+        } else if (event->getType() == InputEventType::CAPTURE) {
             const auto& captureEvent = static_cast<CaptureEvent&>(*event);
             ADD_FAILURE() << "Received capture event, pointerCaptureEnabled = "
                           << (captureEvent.getPointerCaptureEnabled() ? "true" : "false");
-        } else if (event->getType() == AINPUT_EVENT_TYPE_TOUCH_MODE) {
+        } else if (event->getType() == InputEventType::TOUCH_MODE) {
             const auto& touchModeEvent = static_cast<TouchModeEvent&>(*event);
             ADD_FAILURE() << "Received touch mode event, inTouchMode = "
                           << (touchModeEvent.isInTouchMode() ? "true" : "false");
@@ -1200,6 +1204,10 @@
         mInfo.setInputConfig(WindowInfo::InputConfig::NO_INPUT_CHANNEL, noInputChannel);
     }
 
+    void setDisableUserActivity(bool disableUserActivity) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::DISABLE_USER_ACTIVITY, disableUserActivity);
+    }
+
     void setAlpha(float alpha) { mInfo.alpha = alpha; }
 
     void setTouchOcclusionMode(TouchOcclusionMode mode) { mInfo.touchOcclusionMode = mode; }
@@ -1243,12 +1251,11 @@
     void setWindowOffset(float offsetX, float offsetY) { mInfo.transform.set(offsetX, offsetY); }
 
     void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId,
-                     expectedFlags);
+        consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId, expectedFlags);
     }
 
     void consumeKeyUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_UP, expectedDisplayId, expectedFlags);
+        consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, expectedDisplayId, expectedFlags);
     }
 
     void consumeMotionCancel(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
@@ -1270,7 +1277,7 @@
 
     void consumeAnyMotionDown(std::optional<int32_t> expectedDisplayId = std::nullopt,
                               std::optional<int32_t> expectedFlags = std::nullopt) {
-        consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_DOWN, expectedDisplayId,
+        consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN, expectedDisplayId,
                      expectedFlags);
     }
 
@@ -1279,25 +1286,25 @@
                                   int32_t expectedFlags = 0) {
         int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN |
                 (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-        consumeEvent(AINPUT_EVENT_TYPE_MOTION, action, expectedDisplayId, expectedFlags);
+        consumeEvent(InputEventType::MOTION, action, expectedDisplayId, expectedFlags);
     }
 
     void consumeMotionPointerUp(int32_t pointerIdx, int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
                                 int32_t expectedFlags = 0) {
         int32_t action = AMOTION_EVENT_ACTION_POINTER_UP |
                 (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-        consumeEvent(AINPUT_EVENT_TYPE_MOTION, action, expectedDisplayId, expectedFlags);
+        consumeEvent(InputEventType::MOTION, action, expectedDisplayId, expectedFlags);
     }
 
     void consumeMotionUp(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
                          int32_t expectedFlags = 0) {
-        consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_UP, expectedDisplayId,
+        consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_UP, expectedDisplayId,
                      expectedFlags);
     }
 
     void consumeMotionOutside(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
                               int32_t expectedFlags = 0) {
-        consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE, expectedDisplayId,
+        consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE, expectedDisplayId,
                      expectedFlags);
     }
 
@@ -1305,7 +1312,7 @@
                                               int32_t expectedFlags = 0) {
         InputEvent* event = consume();
         ASSERT_NE(nullptr, event);
-        ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType());
+        ASSERT_EQ(InputEventType::MOTION, event->getType());
         const MotionEvent& motionEvent = static_cast<MotionEvent&>(*event);
         EXPECT_EQ(AMOTION_EVENT_ACTION_OUTSIDE, motionEvent.getActionMasked());
         EXPECT_EQ(0.f, motionEvent.getRawPointerCoords(0)->getX());
@@ -1330,7 +1337,7 @@
         ASSERT_THAT(*motionEvent, matcher);
     }
 
-    void consumeEvent(int32_t expectedEventType, int32_t expectedAction,
+    void consumeEvent(InputEventType expectedEventType, int32_t expectedAction,
                       std::optional<int32_t> expectedDisplayId,
                       std::optional<int32_t> expectedFlags) {
         ASSERT_NE(mInputReceiver, nullptr) << "Invalid consume event on window with no receiver";
@@ -1379,9 +1386,8 @@
             ADD_FAILURE() << "Consume failed : no event";
             return nullptr;
         }
-        if (event->getType() != AINPUT_EVENT_TYPE_MOTION) {
-            ADD_FAILURE() << "Instead of motion event, got "
-                          << inputEventTypeToString(event->getType());
+        if (event->getType() != InputEventType::MOTION) {
+            ADD_FAILURE() << "Instead of motion event, got " << *event;
             return nullptr;
         }
         return static_cast<MotionEvent*>(event);
@@ -1464,7 +1470,7 @@
 
 class PointerBuilder {
 public:
-    PointerBuilder(int32_t id, int32_t toolType) {
+    PointerBuilder(int32_t id, ToolType toolType) {
         mProperties.clear();
         mProperties.id = id;
         mProperties.toolType = toolType;
@@ -1558,8 +1564,7 @@
 
         // Set mouse cursor position for the most common cases to avoid boilerplate.
         if (mSource == AINPUT_SOURCE_MOUSE &&
-            !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition) &&
-            mPointers.size() == 1) {
+            !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition)) {
             mRawXCursorPosition = pointerCoords[0].getX();
             mRawYCursorPosition = pointerCoords[0].getY();
         }
@@ -1656,6 +1661,11 @@
         return *this;
     }
 
+    MotionArgsBuilder& classification(MotionClassification classification) {
+        mClassification = classification;
+        return *this;
+    }
+
     NotifyMotionArgs build() {
         std::vector<PointerProperties> pointerProperties;
         std::vector<PointerCoords> pointerCoords;
@@ -1666,15 +1676,14 @@
 
         // Set mouse cursor position for the most common cases to avoid boilerplate.
         if (mSource == AINPUT_SOURCE_MOUSE &&
-            !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition) &&
-            mPointers.size() == 1) {
+            !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition)) {
             mRawXCursorPosition = pointerCoords[0].getX();
             mRawYCursorPosition = pointerCoords[0].getY();
         }
 
         NotifyMotionArgs args(InputEvent::nextId(), mEventTime, /*readTime=*/mEventTime, mDeviceId,
                               mSource, mDisplayId, mPolicyFlags, mAction, mActionButton, mFlags,
-                              AMETA_NONE, mButtonState, MotionClassification::NONE, /*edgeFlags=*/0,
+                              AMETA_NONE, mButtonState, mClassification, /*edgeFlags=*/0,
                               mPointers.size(), pointerProperties.data(), pointerCoords.data(),
                               /*xPrecision=*/0, /*yPrecision=*/0, mRawXCursorPosition,
                               mRawYCursorPosition, mDownTime, /*videoFrames=*/{});
@@ -1693,6 +1702,7 @@
     int32_t mActionButton{0};
     int32_t mButtonState{0};
     int32_t mFlags{0};
+    MotionClassification mClassification{MotionClassification::NONE};
     float mRawXCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
     float mRawYCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
 
@@ -1722,7 +1732,7 @@
                                 .eventTime(eventTime)
                                 .rawXCursorPosition(cursorPosition.x)
                                 .rawYCursorPosition(cursorPosition.y)
-                                .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER)
                                                  .x(position.x)
                                                  .y(position.y))
                                 .build();
@@ -1754,8 +1764,31 @@
     return args;
 }
 
-static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source, int32_t displayId,
-                                           const std::vector<PointF>& points) {
+static NotifyKeyArgs generateSystemShortcutArgs(int32_t action,
+                                                int32_t displayId = ADISPLAY_ID_NONE) {
+    nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    // Define a valid key event.
+    NotifyKeyArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+                       displayId, 0, action, /* flags */ 0, AKEYCODE_C, KEY_C, AMETA_META_ON,
+                       currentTime);
+
+    return args;
+}
+
+static NotifyKeyArgs generateAssistantKeyArgs(int32_t action,
+                                              int32_t displayId = ADISPLAY_ID_NONE) {
+    nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    // Define a valid key event.
+    NotifyKeyArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+                       displayId, 0, action, /* flags */ 0, AKEYCODE_ASSIST, KEY_ASSISTANT,
+                       AMETA_NONE, currentTime);
+
+    return args;
+}
+
+[[nodiscard]] static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source,
+                                                         int32_t displayId,
+                                                         const std::vector<PointF>& points) {
     size_t pointerCount = points.size();
     if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_UP) {
         EXPECT_EQ(1U, pointerCount) << "Actions DOWN and UP can only contain a single pointer";
@@ -1767,7 +1800,7 @@
     for (size_t i = 0; i < pointerCount; i++) {
         pointerProperties[i].clear();
         pointerProperties[i].id = i;
-        pointerProperties[i].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+        pointerProperties[i].toolType = ToolType::FINGER;
 
         pointerCoords[i].clear();
         pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, points[i].x);
@@ -1957,26 +1990,20 @@
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
 
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
-    NotifyMotionArgs args;
     // First touch pointer down on right window
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
-                           .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                                      .build());
     // Second touch pointer down
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
-                           .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(110).y(100))
-                           .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(110).y(100))
+                                      .build());
     // First touch pointer lifts. The second one remains down
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
-
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
-                           .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(110).y(100))
-                           .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(110).y(100))
+                                      .build());
     window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
     window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
     window->consumeMotionEvent(WithMotionAction(POINTER_0_UP));
@@ -2069,8 +2096,8 @@
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
-                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150))
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(100))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(150))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -2085,8 +2112,8 @@
             MotionEventBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
-                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150))
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(100))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(150))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
@@ -2102,7 +2129,7 @@
                                         .displayId(ADISPLAY_ID_DEFAULT)
                                         .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                                         .pointer(PointerBuilder(/* id */ 1,
-                                                                AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                                                ToolType::FINGER)
                                                          .x(100)
                                                          .y(100))
                                         .build(),
@@ -2154,8 +2181,8 @@
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
-                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100))
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(100))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(300).y(100))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -2179,8 +2206,8 @@
     const MotionEvent secondFingerMoveEvent =
             MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
-                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(310).y(110))
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(100))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(310).y(110))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT,
@@ -2267,54 +2294,48 @@
 
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}});
     const int32_t touchDeviceId = 4;
-    NotifyMotionArgs args;
 
     // Two pointers down
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                           .deviceId(touchDeviceId)
-                           .policyFlags(DEFAULT_POLICY_FLAGS)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
-                           .build()));
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .deviceId(touchDeviceId)
+                    .policyFlags(DEFAULT_POLICY_FLAGS)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                    .build());
 
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                           .deviceId(touchDeviceId)
-                           .policyFlags(DEFAULT_POLICY_FLAGS)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
-                           .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(120).y(120))
-                           .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .policyFlags(DEFAULT_POLICY_FLAGS)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120))
+                                      .build());
     spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
     spyWindow->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
     window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
     window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
 
     // Cancel the current gesture. Send the cancel without the default policy flags.
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(AMOTION_EVENT_ACTION_CANCEL, AINPUT_SOURCE_TOUCHSCREEN)
-                           .deviceId(touchDeviceId)
-                           .policyFlags(0)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
-                           .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(120).y(120))
-                           .build()));
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_CANCEL, AINPUT_SOURCE_TOUCHSCREEN)
+                    .deviceId(touchDeviceId)
+                    .policyFlags(0)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                    .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120))
+                    .build());
     spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL));
     window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL));
 
     // We don't need to reset the device to reproduce the issue, but the reset event typically
     // follows, so we keep it here to model the actual listener behaviour more closely.
-    NotifyDeviceResetArgs resetArgs;
-    resetArgs.id = 1; // arbitrary id
-    resetArgs.eventTime = systemTime(SYSTEM_TIME_MONOTONIC);
-    resetArgs.deviceId = touchDeviceId;
-    mDispatcher->notifyDeviceReset(&resetArgs);
+    mDispatcher->notifyDeviceReset({/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC), touchDeviceId});
 
     // Start new gesture
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                           .deviceId(touchDeviceId)
-                           .policyFlags(DEFAULT_POLICY_FLAGS)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
-                           .build()));
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .deviceId(touchDeviceId)
+                    .policyFlags(DEFAULT_POLICY_FLAGS)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                    .build());
     spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
     window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
 
@@ -2358,7 +2379,7 @@
                                         .deviceId(mouseDeviceId)
                                         .downTime(baseTime + 10)
                                         .eventTime(baseTime + 20)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE)
                                                          .x(300)
                                                          .y(100))
                                         .build()));
@@ -2372,7 +2393,7 @@
                                         .deviceId(mouseDeviceId)
                                         .downTime(baseTime + 10)
                                         .eventTime(baseTime + 30)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE)
                                                          .x(110)
                                                          .y(100))
                                         .build()));
@@ -2386,7 +2407,7 @@
                                         .deviceId(touchDeviceId)
                                         .downTime(baseTime + 40)
                                         .eventTime(baseTime + 40)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER)
                                                          .x(100)
                                                          .y(100))
                                         .build()));
@@ -2401,7 +2422,7 @@
                                         .deviceId(touchDeviceId)
                                         .downTime(baseTime + 40)
                                         .eventTime(baseTime + 50)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER)
                                                          .x(100)
                                                          .y(100))
                                         .build()));
@@ -2415,7 +2436,7 @@
                                         .deviceId(touchDeviceId)
                                         .downTime(baseTime + 60)
                                         .eventTime(baseTime + 60)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER)
                                                          .x(300)
                                                          .y(100))
                                         .build()));
@@ -2429,7 +2450,7 @@
                                         .deviceId(touchDeviceId)
                                         .downTime(baseTime + 60)
                                         .eventTime(baseTime + 70)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER)
                                                          .x(300)
                                                          .y(100))
                                         .build()));
@@ -2441,6 +2462,116 @@
 }
 
 /**
+ * Start hovering in a window. While this hover is still active, make another window appear on top.
+ * The top, obstructing window has no input channel, so it's not supposed to receive input.
+ * While the top window is present, the hovering is stopped.
+ * Later, hovering gets resumed again.
+ * Ensure that new hover gesture is handled correctly.
+ * This test reproduces a crash where the HOVER_EXIT event wasn't getting dispatched correctly
+ * to the window that's currently being hovered over.
+ */
+TEST_F(InputDispatcherTest, HoverWhileWindowAppears) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    // Only a single window is present at first
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+    // Start hovering in the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100))
+                                      .build());
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    // Now, an obscuring window appears!
+    sp<FakeWindowHandle> obscuringWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Obscuring window",
+                                       ADISPLAY_ID_DEFAULT,
+                                       /*token=*/std::make_optional<sp<IBinder>>(nullptr));
+    obscuringWindow->setFrame(Rect(0, 0, 200, 200));
+    obscuringWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED);
+    obscuringWindow->setOwnerInfo(SECONDARY_WINDOW_PID, SECONDARY_WINDOW_UID);
+    obscuringWindow->setNoInputChannel(true);
+    obscuringWindow->setFocusable(false);
+    obscuringWindow->setAlpha(1.0);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}});
+
+    // While this new obscuring window is present, the hovering is stopped
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100))
+                                      .build());
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+
+    // Now the obscuring window goes away.
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+    // And a new hover gesture starts.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100))
+                                      .build());
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+}
+
+/**
+ * Same test as 'HoverWhileWindowAppears' above, but here, we also send some HOVER_MOVE events to
+ * the obscuring window.
+ */
+TEST_F(InputDispatcherTest, HoverMoveWhileWindowAppears) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    // Only a single window is present at first
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+    // Start hovering in the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100))
+                                      .build());
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    // Now, an obscuring window appears!
+    sp<FakeWindowHandle> obscuringWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Obscuring window",
+                                       ADISPLAY_ID_DEFAULT,
+                                       /*token=*/std::make_optional<sp<IBinder>>(nullptr));
+    obscuringWindow->setFrame(Rect(0, 0, 200, 200));
+    obscuringWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED);
+    obscuringWindow->setOwnerInfo(SECONDARY_WINDOW_PID, SECONDARY_WINDOW_UID);
+    obscuringWindow->setNoInputChannel(true);
+    obscuringWindow->setFocusable(false);
+    obscuringWindow->setAlpha(1.0);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}});
+
+    // While this new obscuring window is present, the hovering continues. The event can't go to the
+    // bottom window due to obstructed touches, so it should generate HOVER_EXIT for that window.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100))
+                                      .build());
+    obscuringWindow->assertNoEvents();
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+
+    // Now the obscuring window goes away.
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+    // Hovering continues in the same position. The hovering pointer re-enters the bottom window,
+    // so it should generate a HOVER_ENTER
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100))
+                                      .build());
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    // Now the MOVE should be getting dispatched normally
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110))
+                                      .build());
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_MOVE));
+}
+
+/**
  * Two windows: a window on the left and a window on the right.
  * Mouse is clicked on the left window and remains down. Touch is touched on the right and remains
  * down. Then, on the left window, also place second touch pointer down.
@@ -2464,53 +2595,49 @@
     NotifyMotionArgs args;
 
     // Start hovering over the left window
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
-                           .deviceId(mouseDeviceId)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100))
-                           .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                                      .deviceId(mouseDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100))
+                                      .build());
     leftWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId)));
 
     // Mouse down on left window
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
-                           .deviceId(mouseDeviceId)
-                           .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100))
-                           .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .deviceId(mouseDeviceId)
+                                      .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100))
+                                      .build());
 
     leftWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId)));
     leftWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId)));
 
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE)
-                           .deviceId(mouseDeviceId)
-                           .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
-                           .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100))
-                           .build()));
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE)
+                    .deviceId(mouseDeviceId)
+                    .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                    .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+                    .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100))
+                    .build());
     leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
 
     // First touch pointer down on right window
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                           .deviceId(touchDeviceId)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100))
-                           .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .build());
     leftWindow->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
 
     rightWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
 
     // Second touch pointer down on left window
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                           .deviceId(touchDeviceId)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100))
-                           .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
-                           .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(100).y(100))
+                                      .build());
     leftWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
     // This MOVE event is not necessary (doesn't carry any new information), but it's there in the
@@ -2544,57 +2671,52 @@
     NotifyMotionArgs args;
 
     // First touch pointer down
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                           .deviceId(touchDeviceId)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100))
-                           .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .build());
     // Second touch pointer down
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                           .deviceId(touchDeviceId)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100))
-                           .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(350).y(100))
-                           .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100))
+                                      .build());
     // First touch pointer lifts. The second one remains down
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
-                           .deviceId(touchDeviceId)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100))
-                           .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(350).y(100))
-                           .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100))
+                                      .build());
     window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
     window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
     window->consumeMotionEvent(WithMotionAction(POINTER_0_UP));
 
     // Mouse down. The touch should be canceled
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
-                           .deviceId(mouseDeviceId)
-                           .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(320).y(100))
-                           .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .deviceId(mouseDeviceId)
+                                      .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(320).y(100))
+                                      .build());
 
     window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId),
                                      WithPointerCount(1u)));
     window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId)));
 
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE)
-                           .deviceId(mouseDeviceId)
-                           .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
-                           .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(320).y(100))
-                           .build()));
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE)
+                    .deviceId(mouseDeviceId)
+                    .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                    .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+                    .pointer(PointerBuilder(0, ToolType::MOUSE).x(320).y(100))
+                    .build());
     window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
 
     // Second touch pointer down.
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(POINTER_0_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                           .deviceId(touchDeviceId)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100))
-                           .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(350).y(100))
-                           .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_0_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100))
+                                      .build());
     // The pointer_down event should be ignored
     window->assertNoEvents();
 }
@@ -2619,7 +2741,7 @@
               injectMotionEvent(mDispatcher,
                                 MotionEventBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
                                         .deviceId(ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE)
                                                          .x(50)
                                                          .y(50))
                                         .build()));
@@ -2628,11 +2750,10 @@
 
     // Now a real touch comes. Rather than crashing or dropping the real event, the injected pointer
     // should be canceled and the new gesture should take over.
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                           .deviceId(touchDeviceId)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100))
-                           .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .build());
 
     window->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(VIRTUAL_KEYBOARD_ID)));
@@ -2673,7 +2794,7 @@
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
                                                    AINPUT_SOURCE_MOUSE)
                                         .deviceId(mouseDeviceId)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE)
                                                          .x(50)
                                                          .y(50))
                                         .build()));
@@ -2685,7 +2806,7 @@
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(touchDeviceId)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER)
                                                          .x(100)
                                                          .y(100))
                                         .build()));
@@ -2695,7 +2816,7 @@
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(touchDeviceId)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER)
                                                          .x(100)
                                                          .y(100))
                                         .build()));
@@ -2709,7 +2830,7 @@
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(touchDeviceId)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER)
                                                          .x(300)
                                                          .y(100))
                                         .build()));
@@ -2720,10 +2841,10 @@
               injectMotionEvent(mDispatcher,
                                 MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(touchDeviceId)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER)
                                                          .x(300)
                                                          .y(100))
-                                        .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                        .pointer(PointerBuilder(1, ToolType::FINGER)
                                                          .x(100)
                                                          .y(100))
                                         .build()));
@@ -2756,7 +2877,7 @@
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
                                                    AINPUT_SOURCE_STYLUS)
                                         .deviceId(stylusDeviceId)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS)
+                                        .pointer(PointerBuilder(0, ToolType::STYLUS)
                                                          .x(50)
                                                          .y(50))
                                         .build()));
@@ -2768,7 +2889,7 @@
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(touchDeviceId)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER)
                                                          .x(100)
                                                          .y(100))
                                         .build()));
@@ -2781,7 +2902,7 @@
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_STYLUS)
                                         .deviceId(stylusDeviceId)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS)
+                                        .pointer(PointerBuilder(0, ToolType::STYLUS)
                                                          .x(50)
                                                          .y(50))
                                         .build()));
@@ -2794,7 +2915,7 @@
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(touchDeviceId)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER)
                                                          .x(100)
                                                          .y(100))
                                         .build()));
@@ -2806,7 +2927,7 @@
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_STYLUS)
                                         .deviceId(stylusDeviceId)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS)
+                                        .pointer(PointerBuilder(0, ToolType::STYLUS)
                                                          .x(50)
                                                          .y(50))
                                         .build()));
@@ -2834,46 +2955,38 @@
 
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}});
 
-    NotifyMotionArgs args;
-
     // Start hovering with stylus
-    mDispatcher->notifyMotion(
-            &(args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50))
-                             .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
+                                      .build());
     spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
     // Stop hovering
-    mDispatcher->notifyMotion(
-            &(args = MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
-                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50))
-                             .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
+                                      .build());
     spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
 
     // Stylus touches down
-    mDispatcher->notifyMotion(
-            &(args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
-                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50))
-                             .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
+                                      .build());
     spyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
 
     // Stylus goes up
-    mDispatcher->notifyMotion(
-            &(args = MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_STYLUS)
-                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50))
-                             .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
+                                      .build());
     spyWindow->consumeMotionEvent(WithMotionAction(ACTION_UP));
 
     // Again hover
-    mDispatcher->notifyMotion(
-            &(args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50))
-                             .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
+                                      .build());
     spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
     // Stop hovering
-    mDispatcher->notifyMotion(
-            &(args = MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
-                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50))
-                             .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
+                                      .build());
     spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
 
     // No more events
@@ -2902,35 +3015,31 @@
 
     const int32_t mouseDeviceId = 7;
     const int32_t touchDeviceId = 4;
-    NotifyMotionArgs args;
 
     // Hover a bit with mouse first
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
-                           .deviceId(mouseDeviceId)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100))
-                           .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                                      .deviceId(mouseDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100))
+                                      .build());
     spyWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId)));
     window->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId)));
 
     // Start touching
-    mDispatcher->notifyMotion(
-            &(args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                             .deviceId(touchDeviceId)
-                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
-                             .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .build());
     spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
     window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
     spyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
     window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
 
-    mDispatcher->notifyMotion(
-            &(args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
-                             .deviceId(touchDeviceId)
-                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(55).y(55))
-                             .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(55).y(55))
+                                      .build());
     spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
     window->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
 
@@ -2938,20 +3047,18 @@
     EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindow->getToken()));
     window->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
 
-    mDispatcher->notifyMotion(
-            &(args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
-                             .deviceId(touchDeviceId)
-                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(60).y(60))
-                             .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(60).y(60))
+                                      .build());
     spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
 
     // Mouse down
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
-                           .deviceId(mouseDeviceId)
-                           .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100))
-                           .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .deviceId(mouseDeviceId)
+                                      .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100))
+                                      .build());
 
     spyWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId)));
@@ -2959,32 +3066,30 @@
             AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId)));
     window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId)));
 
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE)
-                           .deviceId(mouseDeviceId)
-                           .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
-                           .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100))
-                           .build()));
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE)
+                    .deviceId(mouseDeviceId)
+                    .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                    .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+                    .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100))
+                    .build());
     spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
     window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
 
     // Mouse move!
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE)
-                           .deviceId(mouseDeviceId)
-                           .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(110).y(110))
-                           .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .deviceId(mouseDeviceId)
+                                      .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(110))
+                                      .build());
     spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
     window->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
 
     // Touch move!
-    mDispatcher->notifyMotion(
-            &(args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
-                             .deviceId(touchDeviceId)
-                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(65).y(65))
-                             .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(65).y(65))
+                                      .build());
 
     // No more events
     spyWindow->assertNoEvents();
@@ -3002,16 +3107,15 @@
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", DISPLAY_ID);
 
     mDispatcher->setInputWindows({{DISPLAY_ID, {window}}});
-    NotifyMotionArgs args;
 
     // Touch down on the empty space
-    mDispatcher->notifyMotion(&(args = generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{-1, -1}})));
+    mDispatcher->notifyMotion(generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{-1, -1}}));
 
     mDispatcher->waitForIdle();
     window->assertNoEvents();
 
     // Now touch down on the window with another pointer
-    mDispatcher->notifyMotion(&(args = generateTouchArgs(POINTER_1_DOWN, {{-1, -1}, {10, 10}})));
+    mDispatcher->notifyMotion(generateTouchArgs(POINTER_1_DOWN, {{-1, -1}, {10, 10}}));
     mDispatcher->waitForIdle();
     window->consumeMotionDown();
 }
@@ -3032,16 +3136,15 @@
 
     mDispatcher->setInputWindows({{DISPLAY_ID, {window1, window2}}});
 
-    NotifyMotionArgs args;
     // Touch down on the non-touchable window
-    mDispatcher->notifyMotion(&(args = generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{50, 50}})));
+    mDispatcher->notifyMotion(generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{50, 50}}));
 
     mDispatcher->waitForIdle();
     window1->assertNoEvents();
     window2->assertNoEvents();
 
     // Now touch down on the window with another pointer
-    mDispatcher->notifyMotion(&(args = generateTouchArgs(POINTER_1_DOWN, {{50, 50}, {150, 50}})));
+    mDispatcher->notifyMotion(generateTouchArgs(POINTER_1_DOWN, {{50, 50}, {150, 50}}));
     mDispatcher->waitForIdle();
     window2->consumeMotionDown();
 }
@@ -3061,9 +3164,8 @@
 
     mDispatcher->setInputWindows({{DISPLAY_ID, {window1, window2}}});
 
-    NotifyMotionArgs args;
     // Touch down on the first window
-    mDispatcher->notifyMotion(&(args = generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{50, 50}})));
+    mDispatcher->notifyMotion(generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{50, 50}}));
 
     mDispatcher->waitForIdle();
     InputEvent* inputEvent1 = window1->consume();
@@ -3074,7 +3176,7 @@
     ASSERT_EQ(motionEvent1.getDownTime(), motionEvent1.getEventTime());
 
     // Now touch down on the window with another pointer
-    mDispatcher->notifyMotion(&(args = generateTouchArgs(POINTER_1_DOWN, {{50, 50}, {150, 50}})));
+    mDispatcher->notifyMotion(generateTouchArgs(POINTER_1_DOWN, {{50, 50}, {150, 50}}));
     mDispatcher->waitForIdle();
     InputEvent* inputEvent2 = window2->consume();
     ASSERT_NE(inputEvent2, nullptr);
@@ -3084,14 +3186,12 @@
     ASSERT_EQ(motionEvent2.getDownTime(), motionEvent2.getEventTime());
 
     // Now move the pointer on the second window
-    mDispatcher->notifyMotion(
-            &(args = generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{50, 50}, {151, 51}})));
+    mDispatcher->notifyMotion(generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{50, 50}, {151, 51}}));
     mDispatcher->waitForIdle();
     window2->consumeMotionEvent(WithDownTime(downTimeForWindow2));
 
     // Now add new touch down on the second window
-    mDispatcher->notifyMotion(
-            &(args = generateTouchArgs(POINTER_2_DOWN, {{50, 50}, {151, 51}, {150, 50}})));
+    mDispatcher->notifyMotion(generateTouchArgs(POINTER_2_DOWN, {{50, 50}, {151, 51}, {150, 50}}));
     mDispatcher->waitForIdle();
     window2->consumeMotionEvent(WithDownTime(downTimeForWindow2));
 
@@ -3100,13 +3200,13 @@
     window1->assertNoEvents();
 
     // Now move the pointer on the first window
-    mDispatcher->notifyMotion(&(
-            args = generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{51, 51}, {151, 51}, {150, 50}})));
+    mDispatcher->notifyMotion(
+            generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{51, 51}, {151, 51}, {150, 50}}));
     mDispatcher->waitForIdle();
     window1->consumeMotionEvent(WithDownTime(downTimeForWindow1));
 
-    mDispatcher->notifyMotion(&(
-            args = generateTouchArgs(POINTER_3_DOWN, {{51, 51}, {151, 51}, {150, 50}, {50, 50}})));
+    mDispatcher->notifyMotion(
+            generateTouchArgs(POINTER_3_DOWN, {{51, 51}, {151, 51}, {150, 50}, {50, 50}}));
     mDispatcher->waitForIdle();
     window1->consumeMotionEvent(WithDownTime(downTimeForWindow1));
 }
@@ -3129,9 +3229,7 @@
               injectMotionEvent(mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_MOUSE)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
-                                                         .x(900)
-                                                         .y(400))
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(900).y(400))
                                         .build()));
     windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
 
@@ -3140,9 +3238,7 @@
               injectMotionEvent(mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_MOUSE)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
-                                                         .x(300)
-                                                         .y(400))
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
                                         .build()));
     windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
     windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
@@ -3152,9 +3248,7 @@
               injectMotionEvent(mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE)
                                         .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
-                                                         .x(300)
-                                                         .y(400))
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
                                         .build()));
     windowLeft->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
     windowLeft->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
@@ -3165,9 +3259,7 @@
                                                    AINPUT_SOURCE_MOUSE)
                                         .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
                                         .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
-                                                         .x(300)
-                                                         .y(400))
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
                                         .build()));
     windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
 
@@ -3177,9 +3269,7 @@
                                                    AINPUT_SOURCE_MOUSE)
                                         .buttonState(0)
                                         .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
-                                                         .x(300)
-                                                         .y(400))
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
                                         .build()));
     windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE));
 
@@ -3187,9 +3277,7 @@
               injectMotionEvent(mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_MOUSE)
                                         .buttonState(0)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
-                                                         .x(300)
-                                                         .y(400))
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
                                         .build()));
     windowLeft->consumeMotionUp(ADISPLAY_ID_DEFAULT);
 
@@ -3198,9 +3286,7 @@
               injectMotionEvent(mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_MOUSE)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
-                                                         .x(900)
-                                                         .y(400))
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(900).y(400))
                                         .build()));
     windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
 
@@ -3224,52 +3310,47 @@
 
     const int32_t touchDeviceId = 4;
     const int32_t mouseDeviceId = 6;
-    NotifyMotionArgs args;
 
     // Two pointers down
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                           .deviceId(touchDeviceId)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
-                           .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                                      .build());
 
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                           .deviceId(touchDeviceId)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
-                           .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(120).y(120))
-                           .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120))
+                                      .build());
     window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
     window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
 
     // Inject a series of mouse events for a mouse click
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
-                           .deviceId(mouseDeviceId)
-                           .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(300).y(400))
-                           .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .deviceId(mouseDeviceId)
+                                      .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
+                                      .build());
     window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId),
                                      WithPointerCount(2u)));
     window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId)));
 
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE)
-                           .deviceId(mouseDeviceId)
-                           .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
-                           .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(300).y(400))
-                           .build()));
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE)
+                    .deviceId(mouseDeviceId)
+                    .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                    .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+                    .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
+                    .build());
     window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
 
     // Try to send more touch events while the mouse is down. Since it's a continuation of an
     // already canceled gesture, it should be ignored.
-    mDispatcher->notifyMotion(&(
-            args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
-                           .deviceId(touchDeviceId)
-                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(101).y(101))
-                           .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(121).y(121))
-                           .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(101).y(101))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(121).y(121))
+                                      .build());
     window->assertNoEvents();
 }
 
@@ -3293,7 +3374,7 @@
               injectMotionEvent(mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
                                                    AINPUT_SOURCE_MOUSE)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE)
                                                          .x(100)
                                                          .y(100))
                                         .build()));
@@ -3327,7 +3408,7 @@
               injectMotionEvent(mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
                                                    AINPUT_SOURCE_MOUSE)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE)
                                                          .x(100)
                                                          .y(100))
                                         .build()));
@@ -3337,7 +3418,7 @@
               injectMotionEvent(mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_MOUSE)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE)
                                                          .x(110)
                                                          .y(110))
                                         .build()));
@@ -3356,7 +3437,7 @@
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(SECOND_DEVICE_ID)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER)
                                                          .x(200)
                                                          .y(200))
                                         .build()));
@@ -3380,7 +3461,7 @@
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(SECOND_DEVICE_ID)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER)
                                                          .x(200)
                                                          .y(200))
                                         .build()));
@@ -3397,7 +3478,7 @@
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(SECOND_DEVICE_ID)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER)
                                                          .x(250)
                                                          .y(250))
                                         .build()));
@@ -3412,7 +3493,7 @@
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(SECOND_DEVICE_ID)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER)
                                                          .x(250)
                                                          .y(250))
                                         .build()));
@@ -3441,9 +3522,7 @@
               injectMotionEvent(mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
                                                    AINPUT_SOURCE_MOUSE)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
-                                                         .x(300)
-                                                         .y(400))
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
                                         .build()));
     window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
     // Inject a series of mouse events for a mouse click
@@ -3451,9 +3530,7 @@
               injectMotionEvent(mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE)
                                         .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
-                                                         .x(300)
-                                                         .y(400))
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
                                         .build()));
     window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
     window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
@@ -3464,9 +3541,7 @@
                                                    AINPUT_SOURCE_MOUSE)
                                         .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
                                         .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
-                                                         .x(300)
-                                                         .y(400))
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
                                         .build()));
     window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
 
@@ -3476,9 +3551,7 @@
                                                    AINPUT_SOURCE_MOUSE)
                                         .buttonState(0)
                                         .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
-                                                         .x(300)
-                                                         .y(400))
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
                                         .build()));
     window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE));
 
@@ -3486,19 +3559,17 @@
               injectMotionEvent(mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_MOUSE)
                                         .buttonState(0)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
-                                                         .x(300)
-                                                         .y(400))
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
                                         .build()));
     window->consumeMotionUp(ADISPLAY_ID_DEFAULT);
 
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+    // We already canceled the hovering implicitly by injecting the "DOWN" event without lifting the
+    // hover first. Therefore, injection of HOVER_EXIT is inconsistent, and should fail.
+    ASSERT_EQ(InputEventInjectionResult::FAILED,
               injectMotionEvent(mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT,
                                                    AINPUT_SOURCE_MOUSE)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
-                                                         .x(300)
-                                                         .y(400))
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
                                         .build()));
     window->assertNoEvents();
 }
@@ -3521,7 +3592,7 @@
               injectMotionEvent(mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
                                                    AINPUT_SOURCE_MOUSE)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE)
                                                          .x(300)
                                                          .y(400))
                                         .build()));
@@ -3545,23 +3616,20 @@
 
     const int32_t mouseDeviceId = 7;
     const int32_t touchDeviceId = 4;
-    NotifyMotionArgs args;
 
     // Start hovering with the mouse
-    mDispatcher->notifyMotion(
-            &(args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
-                             .deviceId(mouseDeviceId)
-                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(10).y(10))
-                             .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                                      .deviceId(mouseDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(10).y(10))
+                                      .build());
     window->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId)));
 
     // Touch goes down
-    mDispatcher->notifyMotion(
-            &(args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                             .deviceId(touchDeviceId)
-                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
-                             .build()));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .build());
 
     window->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId)));
@@ -3587,15 +3655,15 @@
                                ADISPLAY_ID_DEFAULT, {{50, 50}});
     motionArgs.xCursorPosition = 50;
     motionArgs.yCursorPosition = 50;
-    mDispatcher->notifyMotion(&motionArgs);
+    mDispatcher->notifyMotion(motionArgs);
     ASSERT_NO_FATAL_FAILURE(
             window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                                              WithSource(AINPUT_SOURCE_MOUSE))));
 
     // Tap on the window
-    motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                    ADISPLAY_ID_DEFAULT, {{10, 10}});
-    mDispatcher->notifyMotion(&motionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                 {{10, 10}}));
     ASSERT_NO_FATAL_FAILURE(
             window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
                                              WithSource(AINPUT_SOURCE_MOUSE))));
@@ -3604,9 +3672,8 @@
             window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
                                              WithSource(AINPUT_SOURCE_TOUCHSCREEN))));
 
-    motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                    ADISPLAY_ID_DEFAULT, {{10, 10}});
-    mDispatcher->notifyMotion(&motionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT, {{10, 10}}));
     ASSERT_NO_FATAL_FAILURE(
             window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
                                              WithSource(AINPUT_SOURCE_TOUCHSCREEN))));
@@ -3633,7 +3700,7 @@
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_MOUSE)
                                         .displayId(ADISPLAY_ID_DEFAULT)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE)
                                                          .x(300)
                                                          .y(600))
                                         .build()));
@@ -3654,7 +3721,7 @@
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_MOUSE)
                                         .displayId(ADISPLAY_ID_DEFAULT)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE)
                                                          .x(400)
                                                          .y(700))
                                         .build()));
@@ -3698,17 +3765,15 @@
 
     window->consumeFocusEvent(true);
 
-    NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyKey(&keyArgs);
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
 
     // Window should receive key down event.
     window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
 
     // When device reset happens, that key stream should be terminated with FLAG_CANCELED
     // on the app side.
-    NotifyDeviceResetArgs args(/*id=*/10, /*eventTime=*/20, DEVICE_ID);
-    mDispatcher->notifyDeviceReset(&args);
-    window->consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT,
+    mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID});
+    window->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT,
                          AKEY_EVENT_FLAG_CANCELED);
 }
 
@@ -3719,22 +3784,43 @@
 
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
 
-    NotifyMotionArgs motionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyMotion(&motionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
 
     // Window should receive motion down event.
     window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
 
     // When device reset happens, that motion stream should be terminated with ACTION_CANCEL
     // on the app side.
-    NotifyDeviceResetArgs args(/*id=*/10, /*eventTime=*/20, DEVICE_ID);
-    mDispatcher->notifyDeviceReset(&args);
+    mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID});
     window->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT)));
 }
 
+TEST_F(InputDispatcherTest, NotifyDeviceResetCancelsHoveringStream) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(10).y(10))
+                                      .build());
+
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    // When device reset happens, that hover stream should be terminated with ACTION_HOVER_EXIT
+    mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID});
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+
+    // After the device has been reset, a new hovering stream can be sent to the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(15).y(15))
+                                      .build());
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+}
+
 TEST_F(InputDispatcherTest, InterceptKeyByPolicy) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
@@ -3746,11 +3832,11 @@
 
     window->consumeFocusEvent(true);
 
-    NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
+    const NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
     const std::chrono::milliseconds interceptKeyTimeout = 50ms;
     const nsecs_t injectTime = keyArgs.eventTime;
     mFakePolicy->setInterceptKeyTimeout(interceptKeyTimeout);
-    mDispatcher->notifyKey(&keyArgs);
+    mDispatcher->notifyKey(keyArgs);
     // The dispatching time should be always greater than or equal to intercept key timeout.
     window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
     ASSERT_TRUE((systemTime(SYSTEM_TIME_MONOTONIC) - injectTime) >=
@@ -3768,11 +3854,9 @@
 
     window->consumeFocusEvent(true);
 
-    NotifyKeyArgs keyDown = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
-    NotifyKeyArgs keyUp = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT);
     mFakePolicy->setInterceptKeyTimeout(150ms);
-    mDispatcher->notifyKey(&keyDown);
-    mDispatcher->notifyKey(&keyUp);
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT));
 
     // Window should receive key event immediately when same key up.
     window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
@@ -3801,10 +3885,9 @@
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {outsideWindow, window}}});
 
     // Tap on first window.
-    NotifyMotionArgs motionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT, {PointF{50, 50}});
-    mDispatcher->notifyMotion(&motionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                 {PointF{50, 50}}));
     window->consumeMotionDown();
     // The coordinates of the tap in 'outsideWindow' are relative to its top left corner.
     // Therefore, we should offset them by (100, 100) relative to the screen's top left corner.
@@ -3835,18 +3918,17 @@
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, secondWindow, thirdWindow}}});
 
     // First pointer lands outside all windows. `window` does not get ACTION_OUTSIDE.
-    NotifyMotionArgs motionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT, {PointF{-10, -10}});
-    mDispatcher->notifyMotion(&motionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                 {PointF{-10, -10}}));
     window->assertNoEvents();
     secondWindow->assertNoEvents();
 
     // The second pointer lands inside `secondWindow`, which should receive a DOWN event.
     // Now, `window` should get ACTION_OUTSIDE.
-    motionArgs = generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                    {PointF{-10, -10}, PointF{105, 105}});
-    mDispatcher->notifyMotion(&motionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT,
+                                                 {PointF{-10, -10}, PointF{105, 105}}));
     const std::map<int32_t, PointF> expectedPointers{{0, PointF{-10, -10}}, {1, PointF{105, 105}}};
     window->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_OUTSIDE), WithPointers(expectedPointers)));
@@ -3855,9 +3937,9 @@
 
     // The third pointer lands inside `thirdWindow`, which should receive a DOWN event. There is
     // no ACTION_OUTSIDE sent to `window` because one has already been sent for this gesture.
-    motionArgs = generateMotionArgs(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                    {PointF{-10, -10}, PointF{105, 105}, PointF{205, 205}});
-    mDispatcher->notifyMotion(&motionArgs);
+    mDispatcher->notifyMotion(
+            generateMotionArgs(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {PointF{-10, -10}, PointF{105, 105}, PointF{205, 205}}));
     window->assertNoEvents();
     secondWindow->consumeMotionMove();
     thirdWindow->consumeMotionDown();
@@ -3869,26 +3951,26 @@
                                                              "Fake Window", ADISPLAY_ID_DEFAULT);
     window->setFocusable(true);
 
-    mDispatcher->onWindowInfosChanged({*window->getInfo()}, {});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
 
     window->consumeFocusEvent(true);
 
-    NotifyKeyArgs keyDown = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
-    NotifyKeyArgs keyUp = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyKey(&keyDown);
-    mDispatcher->notifyKey(&keyUp);
+    const NotifyKeyArgs keyDown = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
+    const NotifyKeyArgs keyUp = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyKey(keyDown);
+    mDispatcher->notifyKey(keyUp);
 
     window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
     window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
 
     // All windows are removed from the display. Ensure that we can no longer dispatch to it.
-    mDispatcher->onWindowInfosChanged({}, {});
+    mDispatcher->onWindowInfosChanged({{}, {}, 0, 0});
 
     window->consumeFocusEvent(false);
 
-    mDispatcher->notifyKey(&keyDown);
-    mDispatcher->notifyKey(&keyUp);
+    mDispatcher->notifyKey(keyDown);
+    mDispatcher->notifyKey(keyUp);
     window->assertNoEvents();
 }
 
@@ -3899,7 +3981,7 @@
     // Ensure window is non-split and have some transform.
     window->setPreventSplitting(true);
     window->setWindowOffset(20, 40);
-    mDispatcher->onWindowInfosChanged({*window->getInfo()}, {});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
@@ -3911,8 +3993,8 @@
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
-                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(-30).y(-50))
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(-30).y(-50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -3927,6 +4009,126 @@
     EXPECT_EQ(-10, event->getY(1)); // -50 + 40
 }
 
+TEST_F(InputDispatcherTest, TouchpadThreeFingerSwipeOnlySentToTrustedOverlays) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 400, 400));
+    sp<FakeWindowHandle> trustedOverlay =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Trusted Overlay",
+                                       ADISPLAY_ID_DEFAULT);
+    trustedOverlay->setSpy(true);
+    trustedOverlay->setTrustedOverlay(true);
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {trustedOverlay, window}}});
+
+    // Start a three-finger touchpad swipe
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(100))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_2_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(100))
+                                      .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(100))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+
+    trustedOverlay->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    trustedOverlay->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
+    trustedOverlay->consumeMotionEvent(WithMotionAction(POINTER_2_DOWN));
+
+    // Move the swipe a bit
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105))
+                                      .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(105))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+
+    trustedOverlay->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+
+    // End the swipe
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_2_UP, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105))
+                                      .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(105))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+
+    trustedOverlay->consumeMotionEvent(WithMotionAction(POINTER_2_UP));
+    trustedOverlay->consumeMotionEvent(WithMotionAction(POINTER_1_UP));
+    trustedOverlay->consumeMotionEvent(WithMotionAction(ACTION_UP));
+
+    window->assertNoEvents();
+}
+
+TEST_F(InputDispatcherTest, TouchpadThreeFingerSwipeNotSentToSingleWindow) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 400, 400));
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+    // Start a three-finger touchpad swipe
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(100))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_2_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(100))
+                                      .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(100))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+
+    // Move the swipe a bit
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105))
+                                      .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(105))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+
+    // End the swipe
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_2_UP, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105))
+                                      .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(105))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+
+    window->assertNoEvents();
+}
+
 /**
  * Ensure the correct coordinate spaces are used by InputDispatcher.
  *
@@ -3938,8 +4140,7 @@
 public:
     void SetUp() override {
         InputDispatcherTest::SetUp();
-        mDisplayInfos.clear();
-        mWindowInfos.clear();
+        removeAllWindowsAndDisplays();
     }
 
     void addDisplayInfo(int displayId, const ui::Transform& transform) {
@@ -3947,12 +4148,17 @@
         info.displayId = displayId;
         info.transform = transform;
         mDisplayInfos.push_back(std::move(info));
-        mDispatcher->onWindowInfosChanged(mWindowInfos, mDisplayInfos);
+        mDispatcher->onWindowInfosChanged({mWindowInfos, mDisplayInfos, 0, 0});
     }
 
     void addWindow(const sp<WindowInfoHandle>& windowHandle) {
         mWindowInfos.push_back(*windowHandle->getInfo());
-        mDispatcher->onWindowInfosChanged(mWindowInfos, mDisplayInfos);
+        mDispatcher->onWindowInfosChanged({mWindowInfos, mDisplayInfos, 0, 0});
+    }
+
+    void removeAllWindowsAndDisplays() {
+        mDisplayInfos.clear();
+        mWindowInfos.clear();
     }
 
     // Set up a test scenario where the display has a scaled projection and there are two windows
@@ -3987,15 +4193,14 @@
     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.
-    NotifyMotionArgs downMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT, {PointF{75, 55}});
-    mDispatcher->notifyMotion(&downMotionArgs);
+    // 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.
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                 {PointF{75, 55}}));
 
     firstWindow->consumeMotionDown();
     secondWindow->assertNoEvents();
@@ -4029,7 +4234,7 @@
     MotionEvent event = MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                                 .displayId(ADISPLAY_ID_DEFAULT)
                                 .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                                .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER)
                                                  .x(untransformedPoint.x)
                                                  .y(untransformedPoint.y))
                                 .build();
@@ -4046,13 +4251,13 @@
     auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
 
     // Send down to the second window.
-    NotifyMotionArgs downMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT, {PointF{150, 220}});
-    mDispatcher->notifyMotion(&downMotionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                 {PointF{150, 220}}));
 
     firstWindow->assertNoEvents();
     const MotionEvent* event = secondWindow->consumeMotion();
+    ASSERT_NE(nullptr, event);
     EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, event->getAction());
 
     // Ensure that the events from the "getRaw" API are in logical display coordinates.
@@ -4066,6 +4271,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};
+        mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                     AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                     {pointInDisplaySpace}));
+        window->consumeMotionDown();
+
+        mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP,
+                                                     AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                     {pointInDisplaySpace}));
+        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};
+        mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                     AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                     {pointInDisplaySpace}));
+
+        mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP,
+                                                     AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                     {pointInDisplaySpace}));
+    }
+    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>)>;
 
@@ -4090,10 +4370,8 @@
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow, wallpaper}}});
 
     // Send down to the first window
-    NotifyMotionArgs downMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyMotion(&downMotionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
 
     // Only the first window should get the down event
     firstWindow->consumeMotionDown();
@@ -4110,10 +4388,8 @@
     wallpaper->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
 
     // Send up event to the second window
-    NotifyMotionArgs upMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyMotion(&upMotionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT));
     // The first  window gets no events and the second gets up
     firstWindow->assertNoEvents();
     secondWindow->consumeMotionUp();
@@ -4149,10 +4425,8 @@
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, firstWindow, secondWindow}}});
 
     // Send down to the first window
-    NotifyMotionArgs downMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyMotion(&downMotionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
     // Only the first window and spy should get the down event
     spyWindow->consumeMotionDown();
     firstWindow->consumeMotionDown();
@@ -4167,10 +4441,8 @@
     secondWindow->consumeMotionDown();
 
     // Send up event to the second window
-    NotifyMotionArgs upMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyMotion(&upMotionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT));
     // The first  window gets no events and the second+spy get up
     firstWindow->assertNoEvents();
     spyWindow->consumeMotionUp();
@@ -4196,19 +4468,16 @@
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}});
 
     // Send down to the first window
-    NotifyMotionArgs downMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT, {touchPoint});
-    mDispatcher->notifyMotion(&downMotionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                 {touchPoint}));
     // Only the first window should get the down event
     firstWindow->consumeMotionDown();
     secondWindow->assertNoEvents();
 
     // Send pointer down to the first window
-    NotifyMotionArgs pointerDownMotionArgs =
-            generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {touchPoint, touchPoint});
-    mDispatcher->notifyMotion(&pointerDownMotionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT, {touchPoint, touchPoint}));
     // Only the first window should get the pointer down event
     firstWindow->consumeMotionPointerDown(1);
     secondWindow->assertNoEvents();
@@ -4223,19 +4492,15 @@
     secondWindow->consumeMotionPointerDown(1);
 
     // Send pointer up to the second window
-    NotifyMotionArgs pointerUpMotionArgs =
-            generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {touchPoint, touchPoint});
-    mDispatcher->notifyMotion(&pointerUpMotionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT, {touchPoint, touchPoint}));
     // The first window gets nothing and the second gets pointer up
     firstWindow->assertNoEvents();
     secondWindow->consumeMotionPointerUp(1);
 
     // Send up event to the second window
-    NotifyMotionArgs upMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyMotion(&upMotionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT));
     // The first window gets nothing and the second gets up
     firstWindow->assertNoEvents();
     secondWindow->consumeMotionUp();
@@ -4266,10 +4531,8 @@
             {{ADISPLAY_ID_DEFAULT, {firstWindow, wallpaper1, secondWindow, wallpaper2}}});
 
     // Send down to the first window
-    NotifyMotionArgs downMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyMotion(&downMotionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
 
     // Only the first window should get the down event
     firstWindow->consumeMotionDown();
@@ -4289,10 +4552,8 @@
     wallpaper2->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
 
     // Send up event to the second window
-    NotifyMotionArgs upMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyMotion(&upMotionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT));
     // The first  window gets no events and the second gets up
     firstWindow->assertNoEvents();
     secondWindow->consumeMotionUp();
@@ -4336,19 +4597,17 @@
     PointF pointInSecond = {300, 600};
 
     // Send down to the first window
-    NotifyMotionArgs firstDownMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT, {pointInFirst});
-    mDispatcher->notifyMotion(&firstDownMotionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                 {pointInFirst}));
     // Only the first window should get the down event
     firstWindow->consumeMotionDown();
     secondWindow->assertNoEvents();
 
     // Send down to the second window
-    NotifyMotionArgs secondDownMotionArgs =
-            generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {pointInFirst, pointInSecond});
-    mDispatcher->notifyMotion(&secondDownMotionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT,
+                                                 {pointInFirst, pointInSecond}));
     // The first window gets a move and the second a down
     firstWindow->consumeMotionMove();
     secondWindow->consumeMotionDown();
@@ -4360,19 +4619,16 @@
     secondWindow->consumeMotionPointerDown(1);
 
     // Send pointer up to the second window
-    NotifyMotionArgs pointerUpMotionArgs =
-            generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {pointInFirst, pointInSecond});
-    mDispatcher->notifyMotion(&pointerUpMotionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT,
+                                                 {pointInFirst, pointInSecond}));
     // The first window gets nothing and the second gets pointer up
     firstWindow->assertNoEvents();
     secondWindow->consumeMotionPointerUp(1);
 
     // Send up event to the second window
-    NotifyMotionArgs upMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyMotion(&upMotionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT));
     // The first window gets nothing and the second gets up
     firstWindow->assertNoEvents();
     secondWindow->consumeMotionUp();
@@ -4402,19 +4658,17 @@
     PointF pointInSecond = {300, 600};
 
     // Send down to the first window
-    NotifyMotionArgs firstDownMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT, {pointInFirst});
-    mDispatcher->notifyMotion(&firstDownMotionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                 {pointInFirst}));
     // Only the first window should get the down event
     firstWindow->consumeMotionDown();
     secondWindow->assertNoEvents();
 
     // Send down to the second window
-    NotifyMotionArgs secondDownMotionArgs =
-            generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {pointInFirst, pointInSecond});
-    mDispatcher->notifyMotion(&secondDownMotionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT,
+                                                 {pointInFirst, pointInSecond}));
     // The first window gets a move and the second a down
     firstWindow->consumeMotionMove();
     secondWindow->consumeMotionDown();
@@ -4429,19 +4683,16 @@
 
     // The rest of the dispatch should proceed as normal
     // Send pointer up to the second window
-    NotifyMotionArgs pointerUpMotionArgs =
-            generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {pointInFirst, pointInSecond});
-    mDispatcher->notifyMotion(&pointerUpMotionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT,
+                                                 {pointInFirst, pointInSecond}));
     // The first window gets MOVE and the second gets pointer up
     firstWindow->consumeMotionMove();
     secondWindow->consumeMotionUp();
 
     // Send up event to the first window
-    NotifyMotionArgs upMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyMotion(&upMotionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT));
     // The first window gets nothing and the second gets up
     firstWindow->consumeMotionUp();
     secondWindow->assertNoEvents();
@@ -4576,11 +4827,98 @@
 
     window->consumeFocusEvent(true);
 
-    NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyKey(&keyArgs);
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
 
     // Window should receive key down event.
     window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+
+    // Should have poked user activity
+    mFakePolicy->assertUserActivityPoked();
+}
+
+TEST_F(InputDispatcherTest, FocusedWindow_DisableUserActivity) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+
+    window->setDisableUserActivity(true);
+    window->setFocusable(true);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    setFocusedWindow(window);
+
+    window->consumeFocusEvent(true);
+
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+
+    // Window should receive key down event.
+    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+
+    // Should have poked user activity
+    mFakePolicy->assertUserActivityNotPoked();
+}
+
+TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceiveSystemShortcut) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+
+    window->setFocusable(true);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    setFocusedWindow(window);
+
+    window->consumeFocusEvent(true);
+
+    mDispatcher->notifyKey(generateSystemShortcutArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->waitForIdle();
+
+    // System key is not passed down
+    window->assertNoEvents();
+
+    // Should have poked user activity
+    mFakePolicy->assertUserActivityPoked();
+}
+
+TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceiveAssistantKey) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+
+    window->setFocusable(true);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    setFocusedWindow(window);
+
+    window->consumeFocusEvent(true);
+
+    mDispatcher->notifyKey(generateAssistantKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->waitForIdle();
+
+    // System key is not passed down
+    window->assertNoEvents();
+
+    // Should have poked user activity
+    mFakePolicy->assertUserActivityPoked();
+}
+
+TEST_F(InputDispatcherTest, FocusedWindow_SystemKeyIgnoresDisableUserActivity) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+
+    window->setDisableUserActivity(true);
+    window->setFocusable(true);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    setFocusedWindow(window);
+
+    window->consumeFocusEvent(true);
+
+    mDispatcher->notifyKey(generateSystemShortcutArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->waitForIdle();
+
+    // System key is not passed down
+    window->assertNoEvents();
+
+    // Should have poked user activity
+    mFakePolicy->assertUserActivityPoked();
 }
 
 TEST_F(InputDispatcherTest, UnfocusedWindow_DoesNotReceiveFocusEventOrKeyEvent) {
@@ -4590,8 +4928,7 @@
 
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
 
-    NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyKey(&keyArgs);
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
     mDispatcher->waitForIdle();
 
     window->assertNoEvents();
@@ -4606,13 +4943,10 @@
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
 
     // Send key
-    NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyKey(&keyArgs);
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
     // Send motion
-    NotifyMotionArgs motionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyMotion(&motionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
 
     // Window should receive only the motion event
     window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
@@ -4639,19 +4973,17 @@
     PointF pointInSecond = {300, 600};
 
     // Send down to the first window
-    NotifyMotionArgs firstDownMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT, {pointInFirst});
-    mDispatcher->notifyMotion(&firstDownMotionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                 {pointInFirst}));
     // Only the first window should get the down event
     firstWindow->consumeMotionDown();
     secondWindow->assertNoEvents();
 
     // Send down to the second window
-    NotifyMotionArgs secondDownMotionArgs =
-            generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {pointInFirst, pointInSecond});
-    mDispatcher->notifyMotion(&secondDownMotionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT,
+                                                 {pointInFirst, pointInSecond}));
     // The first window gets a move and the second a down
     firstWindow->consumeMotionMove();
     secondWindow->consumeMotionDown();
@@ -4661,16 +4993,14 @@
             generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {pointInFirst, pointInSecond});
     pointerUpMotionArgs.flags |= AMOTION_EVENT_FLAG_CANCELED;
-    mDispatcher->notifyMotion(&pointerUpMotionArgs);
+    mDispatcher->notifyMotion(pointerUpMotionArgs);
     // The first window gets move and the second gets cancel.
     firstWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_CANCELED);
     secondWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_CANCELED);
 
     // Send up event.
-    NotifyMotionArgs upMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyMotion(&upMotionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT));
     // The first window gets up and the second gets nothing.
     firstWindow->consumeMotionUp();
     secondWindow->assertNoEvents();
@@ -4703,8 +5033,8 @@
     sp<IBinder> getToken() { return mInputReceiver->getToken(); }
 
     void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_DOWN,
-                                     expectedDisplayId, expectedFlags);
+        mInputReceiver->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId,
+                                     expectedFlags);
     }
 
     std::optional<int32_t> receiveEvent() { return mInputReceiver->receiveEvent(); }
@@ -4712,17 +5042,17 @@
     void finishEvent(uint32_t consumeSeq) { return mInputReceiver->finishEvent(consumeSeq); }
 
     void consumeMotionDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_DOWN,
+        mInputReceiver->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN,
                                      expectedDisplayId, expectedFlags);
     }
 
     void consumeMotionMove(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_MOVE,
+        mInputReceiver->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_MOVE,
                                      expectedDisplayId, expectedFlags);
     }
 
     void consumeMotionUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_UP,
+        mInputReceiver->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_UP,
                                      expectedDisplayId, expectedFlags);
     }
 
@@ -4736,7 +5066,7 @@
     void consumeMotionPointerDown(int32_t pointerIdx) {
         int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN |
                 (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-        mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, action, ADISPLAY_ID_DEFAULT,
+        mInputReceiver->consumeEvent(InputEventType::MOTION, action, ADISPLAY_ID_DEFAULT,
                                      /*expectedFlags=*/0);
     }
 
@@ -4746,8 +5076,8 @@
             ADD_FAILURE() << "No event was produced";
             return nullptr;
         }
-        if (event->getType() != AINPUT_EVENT_TYPE_MOTION) {
-            ADD_FAILURE() << "Received event of type " << event->getType() << " instead of motion";
+        if (event->getType() != InputEventType::MOTION) {
+            ADD_FAILURE() << "Expected MotionEvent, got " << *event;
             return nullptr;
         }
         return static_cast<MotionEvent*>(event);
@@ -4892,7 +5222,7 @@
             generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
                                ADISPLAY_ID_DEFAULT);
 
-    mDispatcher->notifyMotion(&motionArgs);
+    mDispatcher->notifyMotion(motionArgs);
     // Window should receive motion down event.
     window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
 
@@ -4902,8 +5232,8 @@
     motionArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
                                              motionArgs.pointerCoords[0].getX() - 10);
 
-    mDispatcher->notifyMotion(&motionArgs);
-    window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_MOVE, ADISPLAY_ID_DEFAULT,
+    mDispatcher->notifyMotion(motionArgs);
+    window->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_MOVE, ADISPLAY_ID_DEFAULT,
                          /*expectedFlags=*/0);
 }
 
@@ -4971,8 +5301,8 @@
 
     window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
 
-    NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN);
-    mDispatcher->notifyKey(&keyArgs);
+    const NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN);
+    mDispatcher->notifyKey(keyArgs);
 
     InputEvent* event = window->consume();
     ASSERT_NE(event, nullptr);
@@ -5011,12 +5341,12 @@
     displayInfo.displayId = ADISPLAY_ID_DEFAULT;
     displayInfo.transform = transform;
 
-    mDispatcher->onWindowInfosChanged({*window->getInfo()}, {displayInfo});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {displayInfo}, 0, 0});
 
-    NotifyMotionArgs motionArgs =
+    const NotifyMotionArgs motionArgs =
             generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
                                ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyMotion(&motionArgs);
+    mDispatcher->notifyMotion(motionArgs);
 
     InputEvent* event = window->consume();
     ASSERT_NE(event, nullptr);
@@ -5176,7 +5506,8 @@
     setFocusedWindow(windowTop);
     windowTop->consumeFocusEvent(true);
 
-    setFocusedWindow(windowSecond, windowTop);
+    windowTop->editInfo()->focusTransferTarget = windowSecond->getToken();
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowTop, windowSecond}}});
     windowSecond->consumeFocusEvent(true);
     windowTop->consumeFocusEvent(false);
 
@@ -5187,7 +5518,7 @@
     windowSecond->consumeKeyDown(ADISPLAY_ID_NONE);
 }
 
-TEST_F(InputDispatcherTest, SetFocusedWindow_DropRequestFocusTokenNotFocused) {
+TEST_F(InputDispatcherTest, SetFocusedWindow_TransferFocusTokenNotFocusable) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> windowTop =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
@@ -5196,15 +5527,17 @@
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
 
     windowTop->setFocusable(true);
-    windowSecond->setFocusable(true);
+    windowSecond->setFocusable(false);
+    windowTop->editInfo()->focusTransferTarget = windowSecond->getToken();
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowTop, windowSecond}}});
-    setFocusedWindow(windowSecond, windowTop);
+    setFocusedWindow(windowTop);
+    windowTop->consumeFocusEvent(true);
 
-    ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, injectKeyDown(mDispatcher))
-            << "Inject key event should return InputEventInjectionResult::TIMED_OUT";
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
+            << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
 
     // Event should be dropped.
-    windowTop->assertNoEvents();
+    windowTop->consumeKeyDown(ADISPLAY_ID_NONE);
     windowSecond->assertNoEvents();
 }
 
@@ -5310,17 +5643,17 @@
             {{ADISPLAY_ID_DEFAULT, {slipperyExitWindow, slipperyEnterWindow}}});
 
     // Use notifyMotion instead of injecting to avoid dealing with injection permissions
-    NotifyMotionArgs args = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                               ADISPLAY_ID_DEFAULT, {{50, 50}});
-    mDispatcher->notifyMotion(&args);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                 {{50, 50}}));
     slipperyExitWindow->consumeMotionDown();
     slipperyExitWindow->setFrame(Rect(70, 70, 100, 100));
     mDispatcher->setInputWindows(
             {{ADISPLAY_ID_DEFAULT, {slipperyExitWindow, slipperyEnterWindow}}});
 
-    args = generateMotionArgs(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                              ADISPLAY_ID_DEFAULT, {{51, 51}});
-    mDispatcher->notifyMotion(&args);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_MOVE,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                 {{51, 51}}));
 
     slipperyExitWindow->consumeMotionCancel();
 
@@ -5328,6 +5661,46 @@
                                            AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
 }
 
+/**
+ * Two windows, one on the left and another on the right. The left window is slippery. The right
+ * window isn't eligible to receive touch because it specifies InputConfig::DROP_INPUT. When the
+ * touch moves from the left window into the right window, the gesture should continue to go to the
+ * left window. Touch shouldn't slip because the right window can't receive touches. This test
+ * reproduces a crash.
+ */
+TEST_F(InputDispatcherTest, TouchSlippingIntoWindowThatDropsTouches) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    sp<FakeWindowHandle> leftSlipperyWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    leftSlipperyWindow->setSlippery(true);
+    leftSlipperyWindow->setFrame(Rect(0, 0, 100, 100));
+
+    sp<FakeWindowHandle> rightDropTouchesWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    rightDropTouchesWindow->setFrame(Rect(100, 0, 200, 100));
+    rightDropTouchesWindow->setDropInput(true);
+
+    mDispatcher->setInputWindows(
+            {{ADISPLAY_ID_DEFAULT, {leftSlipperyWindow, rightDropTouchesWindow}}});
+
+    // Start touch in the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .build());
+    leftSlipperyWindow->consumeMotionDown();
+
+    // And move it into the right window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .build());
+
+    // Since the right window isn't eligible to receive input, touch does not slip.
+    // The left window continues to receive the gesture.
+    leftSlipperyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+    rightDropTouchesWindow->assertNoEvents();
+}
+
 class InputDispatcherKeyRepeatTest : public InputDispatcherTest {
 protected:
     static constexpr nsecs_t KEY_REPEAT_TIMEOUT = 40 * 1000000; // 40 ms
@@ -5337,9 +5710,10 @@
     sp<FakeWindowHandle> mWindow;
 
     virtual void SetUp() override {
-        mFakePolicy = sp<FakeInputDispatcherPolicy>::make();
+        mFakePolicy = std::make_unique<FakeInputDispatcherPolicy>();
         mFakePolicy->setKeyRepeatConfiguration(KEY_REPEAT_TIMEOUT, KEY_REPEAT_DELAY);
-        mDispatcher = std::make_unique<InputDispatcher>(mFakePolicy);
+        mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy);
+        mDispatcher->requestRefreshConfiguration();
         mDispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
         ASSERT_EQ(OK, mDispatcher->start());
 
@@ -5360,7 +5734,7 @@
         NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
         keyArgs.deviceId = deviceId;
         keyArgs.policyFlags |= POLICY_FLAG_TRUSTED; // Otherwise it won't generate repeat event
-        mDispatcher->notifyKey(&keyArgs);
+        mDispatcher->notifyKey(keyArgs);
 
         // Window should receive key down event.
         mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
@@ -5371,8 +5745,7 @@
         InputEvent* repeatEvent = mWindow->consume();
         ASSERT_NE(nullptr, repeatEvent);
 
-        uint32_t eventType = repeatEvent->getType();
-        ASSERT_EQ(AINPUT_EVENT_TYPE_KEY, eventType);
+        ASSERT_EQ(InputEventType::KEY, repeatEvent->getType());
 
         KeyEvent* repeatKeyEvent = static_cast<KeyEvent*>(repeatEvent);
         uint32_t eventAction = repeatKeyEvent->getAction();
@@ -5384,10 +5757,10 @@
         NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT);
         keyArgs.deviceId = deviceId;
         keyArgs.policyFlags |= POLICY_FLAG_TRUSTED; // Unless it won't generate repeat event
-        mDispatcher->notifyKey(&keyArgs);
+        mDispatcher->notifyKey(keyArgs);
 
         // Window should receive key down event.
-        mWindow->consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT,
+        mWindow->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT,
                               /*expectedFlags=*/0);
     }
 };
@@ -5447,8 +5820,7 @@
 TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_StopsKeyRepeatAfterDisableInputDevice) {
     sendAndConsumeKeyDown(DEVICE_ID);
     expectKeyRepeatOnce(/*repeatCount=*/1);
-    NotifyDeviceResetArgs args(/*id=*/10, /*eventTime=*/20, DEVICE_ID);
-    mDispatcher->notifyDeviceReset(&args);
+    mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID});
     mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT,
                           AKEY_EVENT_FLAG_CANCELED | AKEY_EVENT_FLAG_LONG_PRESS);
     mWindow->assertNoEvents();
@@ -5560,7 +5932,7 @@
     mDispatcher->setInputWindows({{SECOND_DISPLAY_ID, {}}});
 
     // Old focus should receive a cancel event.
-    windowInSecondary->consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_NONE,
+    windowInSecondary->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_NONE,
                                     AKEY_EVENT_FLAG_CANCELED);
 
     // Test inject a key down, should timeout because of no target window.
@@ -5700,10 +6072,10 @@
 
         motionArgs =
                 generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, displayId);
-        mDispatcher->notifyMotion(&motionArgs);
+        mDispatcher->notifyMotion(motionArgs);
         motionArgs =
                 generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, displayId);
-        mDispatcher->notifyMotion(&motionArgs);
+        mDispatcher->notifyMotion(motionArgs);
         ASSERT_TRUE(mDispatcher->waitForIdle());
         if (expectToBeFiltered) {
             const auto xy = transform.transform(motionArgs.pointerCoords->getXYValue());
@@ -5717,9 +6089,9 @@
         NotifyKeyArgs keyArgs;
 
         keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN);
-        mDispatcher->notifyKey(&keyArgs);
+        mDispatcher->notifyKey(keyArgs);
         keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP);
-        mDispatcher->notifyKey(&keyArgs);
+        mDispatcher->notifyKey(keyArgs);
         ASSERT_TRUE(mDispatcher->waitForIdle());
 
         if (expectToBeFiltered) {
@@ -5779,7 +6151,7 @@
     displayInfos[1].displayId = SECOND_DISPLAY_ID;
     displayInfos[1].transform = secondDisplayTransform;
 
-    mDispatcher->onWindowInfosChanged({}, displayInfos);
+    mDispatcher->onWindowInfosChanged({{}, displayInfos, 0, 0});
 
     // Enable InputFilter
     mDispatcher->setInputFilterEnabled(true);
@@ -5831,7 +6203,7 @@
         InputEvent* received = mWindow->consume();
         ASSERT_NE(nullptr, received);
         ASSERT_EQ(resolvedDeviceId, received->getDeviceId());
-        ASSERT_EQ(received->getType(), AINPUT_EVENT_TYPE_KEY);
+        ASSERT_EQ(received->getType(), InputEventType::KEY);
         KeyEvent& keyEvent = static_cast<KeyEvent&>(*received);
         ASSERT_EQ(flags, keyEvent.getFlags());
     }
@@ -5866,7 +6238,7 @@
         InputEvent* received = mWindow->consume();
         ASSERT_NE(nullptr, received);
         ASSERT_EQ(resolvedDeviceId, received->getDeviceId());
-        ASSERT_EQ(received->getType(), AINPUT_EVENT_TYPE_MOTION);
+        ASSERT_EQ(received->getType(), InputEventType::MOTION);
         MotionEvent& motionEvent = static_cast<MotionEvent&>(*received);
         ASSERT_EQ(flags, motionEvent.getFlags());
     }
@@ -5997,7 +6369,7 @@
     const MotionEvent event =
             MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(20).y(20))
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(20).y(20))
                     .addFlag(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, event))
@@ -6047,9 +6419,8 @@
         ASSERT_NE(nullptr, event) << name.c_str()
                                   << ": consumer should have returned non-NULL event.";
 
-        ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType())
-                << name.c_str() << "expected " << inputEventTypeToString(AINPUT_EVENT_TYPE_MOTION)
-                << " event, got " << inputEventTypeToString(event->getType()) << " event";
+        ASSERT_EQ(InputEventType::MOTION, event->getType())
+                << name.c_str() << ": expected MotionEvent, got " << *event;
 
         const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event);
         assertMotionAction(expectedAction, motionEvent.getAction());
@@ -6070,9 +6441,8 @@
 
     void touchAndAssertPositions(int32_t action, const std::vector<PointF>& touchedPoints,
                                  std::vector<PointF> expectedPoints) {
-        NotifyMotionArgs motionArgs = generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN,
-                                                         ADISPLAY_ID_DEFAULT, touchedPoints);
-        mDispatcher->notifyMotion(&motionArgs);
+        mDispatcher->notifyMotion(generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN,
+                                                     ADISPLAY_ID_DEFAULT, touchedPoints));
 
         // Always consume from window1 since it's the window that has the InputReceiver
         consumeMotionEvent(mWindow1, action, expectedPoints);
@@ -6200,6 +6570,28 @@
     touchAndAssertPositions(AMOTION_EVENT_ACTION_MOVE, touchedPoints, expectedPoints);
 }
 
+/**
+ * When one of the windows is slippery, the touch should not slip into the other window with the
+ * same input channel.
+ */
+TEST_F(InputDispatcherMultiWindowSameTokenTests, TouchDoesNotSlipEvenIfSlippery) {
+    mWindow1->setSlippery(true);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow1, mWindow2}}});
+
+    // Touch down in window 1
+    mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT, {{50, 50}}));
+    consumeMotionEvent(mWindow1, ACTION_DOWN, {{50, 50}});
+
+    // Move touch to be above window 2. Even though window 1 is slippery, touch should not slip.
+    // That means the gesture should continue normally, without any ACTION_CANCEL or ACTION_DOWN
+    // getting generated.
+    mDispatcher->notifyMotion(generateMotionArgs(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT, {{150, 150}}));
+
+    consumeMotionEvent(mWindow1, ACTION_MOVE, {{150, 150}});
+}
+
 class InputDispatcherSingleWindowAnr : public InputDispatcherTest {
     virtual void SetUp() override {
         InputDispatcherTest::SetUp();
@@ -6746,7 +7138,7 @@
                                FOCUSED_WINDOW_LOCATION))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mFocusedWindow->consumeMotionDown();
-    mUnfocusedWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
+    mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
                                    ADISPLAY_ID_DEFAULT, /*flags=*/0);
     // We consumed all events, so no ANR
     ASSERT_TRUE(mDispatcher->waitForIdle());
@@ -6820,7 +7212,7 @@
 // At the same time, FLAG_WATCH_OUTSIDE_TOUCH targets should not receive any events.
 TEST_F(InputDispatcherMultiWindowAnr, DuringAnr_SecondTapIsIgnored) {
     tapOnFocusedWindow();
-    mUnfocusedWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
+    mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
                                    ADISPLAY_ID_DEFAULT, /*flags=*/0);
     // Receive the events, but don't respond
     std::optional<uint32_t> downEventSequenceNum = mFocusedWindow->receiveEvent(); // ACTION_DOWN
@@ -6945,17 +7337,16 @@
 // The other window should not be affected by that.
 TEST_F(InputDispatcherMultiWindowAnr, SplitTouch_SingleWindowAnr) {
     // Touch Window 1
-    NotifyMotionArgs motionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT, {FOCUSED_WINDOW_LOCATION});
-    mDispatcher->notifyMotion(&motionArgs);
-    mUnfocusedWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                 {FOCUSED_WINDOW_LOCATION}));
+    mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
                                    ADISPLAY_ID_DEFAULT, /*flags=*/0);
 
     // Touch Window 2
-    motionArgs = generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                    {FOCUSED_WINDOW_LOCATION, UNFOCUSED_WINDOW_LOCATION});
-    mDispatcher->notifyMotion(&motionArgs);
+    mDispatcher->notifyMotion(
+            generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {FOCUSED_WINDOW_LOCATION, UNFOCUSED_WINDOW_LOCATION}));
 
     const std::chrono::duration timeout =
             mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
@@ -6970,7 +7361,7 @@
     ASSERT_TRUE(moveOrCancelSequenceNum);
     mFocusedWindow->finishEvent(*moveOrCancelSequenceNum);
     ASSERT_NE(nullptr, event);
-    ASSERT_EQ(event->getType(), AINPUT_EVENT_TYPE_MOTION);
+    ASSERT_EQ(event->getType(), InputEventType::MOTION);
     MotionEvent& motionEvent = static_cast<MotionEvent&>(*event);
     if (motionEvent.getAction() == AMOTION_EVENT_ACTION_MOVE) {
         mFocusedWindow->consumeMotionCancel();
@@ -7027,10 +7418,9 @@
     std::this_thread::sleep_for(10ms);
 
     // Touch unfocused window. This should force the pending key to get dropped.
-    NotifyMotionArgs motionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT, {UNFOCUSED_WINDOW_LOCATION});
-    mDispatcher->notifyMotion(&motionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                 {UNFOCUSED_WINDOW_LOCATION}));
 
     // We do not consume the motion right away, because that would require dispatcher to first
     // process (== drop) the key event, and by that time, ANR will be raised.
@@ -7085,10 +7475,9 @@
 TEST_F(InputDispatcherMultiWindowOcclusionTests, NoInputChannelFeature_DropsTouches) {
     PointF touchedPoint = {10, 10};
 
-    NotifyMotionArgs motionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT, {touchedPoint});
-    mDispatcher->notifyMotion(&motionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                 {touchedPoint}));
 
     mNoInputWindow->assertNoEvents();
     // Even though the window 'mNoInputWindow' positioned above 'mBottomWindow' does not have
@@ -7113,10 +7502,9 @@
 
     PointF touchedPoint = {10, 10};
 
-    NotifyMotionArgs motionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT, {touchedPoint});
-    mDispatcher->notifyMotion(&motionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                 {touchedPoint}));
 
     mNoInputWindow->assertNoEvents();
     mBottomWindow->assertNoEvents();
@@ -7289,8 +7677,7 @@
     }
 
     void notifyPointerCaptureChanged(const PointerCaptureRequest& request) {
-        const NotifyPointerCaptureChangedArgs args = generatePointerCaptureChangedArgs(request);
-        mDispatcher->notifyPointerCaptureChanged(&args);
+        mDispatcher->notifyPointerCaptureChanged(generatePointerCaptureChangedArgs(request));
     }
 
     PointerCaptureRequest requestAndVerifyPointerCapture(const sp<FakeWindowHandle>& window,
@@ -7466,10 +7853,9 @@
     }
 
     void touch(const std::vector<PointF>& points = {PointF{100, 200}}) {
-        NotifyMotionArgs args =
-                generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                   ADISPLAY_ID_DEFAULT, points);
-        mDispatcher->notifyMotion(&args);
+        mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                     AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                     points));
     }
 };
 
@@ -7858,7 +8244,7 @@
                                   MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
                                                      AINPUT_SOURCE_STYLUS)
                                           .buttonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)
-                                          .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS)
+                                          .pointer(PointerBuilder(0, ToolType::STYLUS)
                                                            .x(50)
                                                            .y(50))
                                           .build()));
@@ -7870,7 +8256,7 @@
                                   MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE)
                                           .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
                                           .pointer(PointerBuilder(MOUSE_POINTER_ID,
-                                                                  AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                                  ToolType::MOUSE)
                                                            .x(50)
                                                            .y(50))
                                           .build()));
@@ -7959,8 +8345,8 @@
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
-                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(60).y(60))
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(60).y(60))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -8014,9 +8400,7 @@
               injectMotionEvent(mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_STYLUS)
                                         .buttonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS)
-                                                         .x(50)
-                                                         .y(50))
+                                        .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
                                         .build()))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
@@ -8028,9 +8412,7 @@
               injectMotionEvent(mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_STYLUS)
                                         .buttonState(0)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS)
-                                                         .x(150)
-                                                         .y(50))
+                                        .pointer(PointerBuilder(0, ToolType::STYLUS).x(150).y(50))
                                         .build()))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
@@ -8043,9 +8425,7 @@
               injectMotionEvent(mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_STYLUS)
                                         .buttonState(0)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS)
-                                                         .x(150)
-                                                         .y(50))
+                                        .pointer(PointerBuilder(0, ToolType::STYLUS).x(150).y(50))
                                         .build()))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
@@ -8103,8 +8483,8 @@
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
-                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(75).y(50))
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(75).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -8130,8 +8510,8 @@
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
-                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(50))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -8146,8 +8526,8 @@
     const MotionEvent secondFingerMoveEvent =
             MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
-                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(50))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT,
@@ -8160,8 +8540,8 @@
     const MotionEvent secondFingerUpEvent =
             MotionEventBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
-                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(50))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
@@ -8186,11 +8566,9 @@
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .displayId(SECOND_DISPLAY_ID)
-                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
-                                                         .x(100)
-                                                         .y(100))
+                                        .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
                                         .build()));
-    windowInSecondary->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_DOWN,
+    windowInSecondary->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN,
                                     SECOND_DISPLAY_ID, /*expectedFlag=*/0);
     // Update window again.
     mDispatcher->setInputWindows({{SECOND_DISPLAY_ID, {windowInSecondary}}});
@@ -8232,7 +8610,7 @@
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
                                         .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
                                         .pointer(PointerBuilder(MOUSE_POINTER_ID,
-                                                                AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                                ToolType::MOUSE)
                                                          .x(50)
                                                          .y(50))
                                         .build()))
@@ -8247,7 +8625,7 @@
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
                                         .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
                                         .pointer(PointerBuilder(MOUSE_POINTER_ID,
-                                                                AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                                ToolType::MOUSE)
                                                          .x(150)
                                                          .y(50))
                                         .build()))
@@ -8262,7 +8640,7 @@
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_MOUSE)
                                         .buttonState(0)
                                         .pointer(PointerBuilder(MOUSE_POINTER_ID,
-                                                                AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                                ToolType::MOUSE)
                                                          .x(150)
                                                          .y(50))
                                         .build()))
@@ -8287,27 +8665,22 @@
     window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
 
     // With the flag set, window should not get any input
-    NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyKey(&keyArgs);
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
     window->assertNoEvents();
 
-    NotifyMotionArgs motionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyMotion(&motionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
     window->assertNoEvents();
 
     // With the flag cleared, the window should get input
     window->setDropInput(false);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
 
-    keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyKey(&keyArgs);
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT));
     window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
 
-    motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                    ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyMotion(&motionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
     window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
     window->assertNoEvents();
 }
@@ -8333,27 +8706,22 @@
     window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
 
     // With the flag set, window should not get any input
-    NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyKey(&keyArgs);
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
     window->assertNoEvents();
 
-    NotifyMotionArgs motionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyMotion(&motionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
     window->assertNoEvents();
 
     // With the flag cleared, the window should get input
     window->setDropInputIfObscured(false);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}});
 
-    keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyKey(&keyArgs);
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT));
     window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
 
-    motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                    ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyMotion(&motionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
     window->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
     window->assertNoEvents();
 }
@@ -8379,26 +8747,21 @@
     window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
 
     // With the flag set, window should not get any input
-    NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyKey(&keyArgs);
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
     window->assertNoEvents();
 
-    NotifyMotionArgs motionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyMotion(&motionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
     window->assertNoEvents();
 
     // When the window is no longer obscured because it went on top, it should get input
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, obscuringWindow}}});
 
-    keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyKey(&keyArgs);
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT));
     window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
 
-    motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                    ADISPLAY_ID_DEFAULT);
-    mDispatcher->notifyMotion(&motionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
     window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
     window->assertNoEvents();
 }
@@ -8734,8 +9097,8 @@
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
-                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -8766,8 +9129,8 @@
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
-                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -8805,8 +9168,8 @@
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(200))
-                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -8928,8 +9291,8 @@
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(200))
-                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -8943,9 +9306,9 @@
             MotionEventBuilder(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(200))
-                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
-                    .pointer(PointerBuilder(/*id=*/2, AMOTION_EVENT_TOOL_TYPE_FINGER).x(-5).y(-5))
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER).x(-5).y(-5))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               injectMotionEvent(mDispatcher, thirdFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -8979,8 +9342,8 @@
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150))
-                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(150))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(10).y(10))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -8994,9 +9357,9 @@
             MotionEventBuilder(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150))
-                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
-                    .pointer(PointerBuilder(/*id=*/2, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(150))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(10).y(10))
+                    .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, thirdFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -9040,8 +9403,8 @@
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
-                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(10).y(10))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -9087,8 +9450,8 @@
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
-                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150))
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(10).y(10))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(150))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -9132,18 +9495,17 @@
     }
 
     void sendFingerEvent(int32_t action) {
-        NotifyMotionArgs motionArgs =
+        mDispatcher->notifyMotion(
                 generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
-                                   ADISPLAY_ID_DEFAULT, {PointF{20, 20}});
-        mDispatcher->notifyMotion(&motionArgs);
+                                   ADISPLAY_ID_DEFAULT, {PointF{20, 20}}));
     }
 
     void sendStylusEvent(int32_t action) {
         NotifyMotionArgs motionArgs =
                 generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
                                    ADISPLAY_ID_DEFAULT, {PointF{30, 40}});
-        motionArgs.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
-        mDispatcher->notifyMotion(&motionArgs);
+        motionArgs.pointerProperties[0].toolType = ToolType::STYLUS;
+        mDispatcher->notifyMotion(motionArgs);
     }
 };
 
diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp
index ae30006..ad48a79 100644
--- a/services/inputflinger/tests/InputMapperTest.cpp
+++ b/services/inputflinger/tests/InputMapperTest.cpp
@@ -50,12 +50,12 @@
     mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, key, value);
 }
 
-std::list<NotifyArgs> InputMapperTest::configureDevice(uint32_t changes) {
-    if (!changes ||
-        (changes &
-         (InputReaderConfiguration::CHANGE_DISPLAY_INFO |
-          InputReaderConfiguration::CHANGE_POINTER_CAPTURE |
-          InputReaderConfiguration::CHANGE_DEVICE_TYPE))) {
+std::list<NotifyArgs> InputMapperTest::configureDevice(ConfigurationChanges changes) {
+    using namespace ftl::flag_operators;
+    if (!changes.any() ||
+        (changes.any(InputReaderConfiguration::Change::DISPLAY_INFO |
+                     InputReaderConfiguration::Change::POINTER_CAPTURE |
+                     InputReaderConfiguration::Change::DEVICE_TYPE))) {
         mReader->requestRefreshConfiguration(changes);
         mReader->loopOnce();
     }
@@ -94,7 +94,7 @@
                                                    ViewportType viewportType) {
     mFakePolicy->addDisplayViewport(displayId, width, height, orientation, /* isActive= */ true,
                                     uniqueId, physicalPort, viewportType);
-    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
 }
 
 void InputMapperTest::clearViewports() {
diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h
index 63ca44c..2b6655c 100644
--- a/services/inputflinger/tests/InputMapperTest.h
+++ b/services/inputflinger/tests/InputMapperTest.h
@@ -54,14 +54,15 @@
     void TearDown() override;
 
     void addConfigurationProperty(const char* key, const char* value);
-    std::list<NotifyArgs> configureDevice(uint32_t changes);
+    std::list<NotifyArgs> configureDevice(ConfigurationChanges changes);
     std::shared_ptr<InputDevice> newDevice(int32_t deviceId, const std::string& name,
                                            const std::string& location, int32_t eventHubId,
                                            ftl::Flags<InputDeviceClass> classes, int bus = 0);
     template <class T, typename... Args>
     T& addMapperAndConfigure(Args... args) {
-        T& mapper = mDevice->addMapper<T>(EVENTHUB_ID, args...);
-        configureDevice(0);
+        T& mapper =
+                mDevice->addMapper<T>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), args...);
+        configureDevice(/*changes=*/{});
         std::list<NotifyArgs> resetArgList = mDevice->reset(ARBITRARY_TIME);
         resetArgList += mapper.reset(ARBITRARY_TIME);
         // Loop the reader to flush the input listener queue.
@@ -72,6 +73,17 @@
         return mapper;
     }
 
+    template <class T, typename... Args>
+    T& constructAndAddMapper(Args... args) {
+        // ensure a device entry exists for this eventHubId
+        mDevice->addEmptyEventHubDevice(EVENTHUB_ID);
+        // configure the empty device
+        configureDevice(/*changes=*/{});
+
+        return mDevice->constructAndAddMapper<T>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+                                                 args...);
+    }
+
     void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height,
                                       ui::Rotation orientation, const std::string& uniqueId,
                                       std::optional<uint8_t> physicalPort,
diff --git a/services/inputflinger/tests/InputProcessorConverter_test.cpp b/services/inputflinger/tests/InputProcessorConverter_test.cpp
index 161a24f..4b42f4b 100644
--- a/services/inputflinger/tests/InputProcessorConverter_test.cpp
+++ b/services/inputflinger/tests/InputProcessorConverter_test.cpp
@@ -30,7 +30,7 @@
     // Create a basic motion event for testing
     PointerProperties properties;
     properties.id = 0;
-    properties.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+    properties.toolType = ToolType::FINGER;
 
     PointerCoords coords;
     coords.clear();
diff --git a/services/inputflinger/tests/InputProcessor_test.cpp b/services/inputflinger/tests/InputProcessor_test.cpp
index b6deed8..3b7cbfa 100644
--- a/services/inputflinger/tests/InputProcessor_test.cpp
+++ b/services/inputflinger/tests/InputProcessor_test.cpp
@@ -37,7 +37,7 @@
     // Create a basic motion event for testing
     PointerProperties properties;
     properties.id = 0;
-    properties.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+    properties.toolType = ToolType::FINGER;
 
     PointerCoords coords;
     coords.clear();
@@ -72,7 +72,7 @@
     // Create a basic configuration change and send to processor
     NotifyConfigurationChangedArgs args(/*sequenceNum=*/1, /*eventTime=*/2);
 
-    mProcessor->notifyConfigurationChanged(&args);
+    mProcessor->notifyConfigurationChanged(args);
     NotifyConfigurationChangedArgs outArgs;
     ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyConfigurationChangedWasCalled(&outArgs));
     ASSERT_EQ(args, outArgs);
@@ -85,10 +85,8 @@
                        AKEY_EVENT_ACTION_DOWN, /*flags=*/4, AKEYCODE_HOME, /*scanCode=*/5,
                        AMETA_NONE, /*downTime=*/6);
 
-    mProcessor->notifyKey(&args);
-    NotifyKeyArgs outArgs;
-    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyKeyWasCalled(&outArgs));
-    ASSERT_EQ(args, outArgs);
+    mProcessor->notifyKey(args);
+    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyKeyWasCalled(testing::Eq(args)));
 }
 
 /**
@@ -97,10 +95,8 @@
  */
 TEST_F(InputProcessorTest, SendToNextStage_NotifyMotionArgs) {
     NotifyMotionArgs motionArgs = generateBasicMotionArgs();
-    mProcessor->notifyMotion(&motionArgs);
-    NotifyMotionArgs args;
-    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(motionArgs, args);
+    mProcessor->notifyMotion(motionArgs);
+    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyMotionWasCalled(testing::Eq(motionArgs)));
 }
 
 /**
@@ -111,7 +107,7 @@
     NotifySwitchArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*policyFlags=*/3,
                           /*switchValues=*/4, /*switchMask=*/5);
 
-    mProcessor->notifySwitch(&args);
+    mProcessor->notifySwitch(args);
     NotifySwitchArgs outArgs;
     ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifySwitchWasCalled(&outArgs));
     ASSERT_EQ(args, outArgs);
@@ -124,7 +120,7 @@
 TEST_F(InputProcessorTest, SendToNextStage_NotifyDeviceResetArgs) {
     NotifyDeviceResetArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*deviceId=*/3);
 
-    mProcessor->notifyDeviceReset(&args);
+    mProcessor->notifyDeviceReset(args);
     NotifyDeviceResetArgs outArgs;
     ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyDeviceResetWasCalled(&outArgs));
     ASSERT_EQ(args, outArgs);
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 0855683..9fbe762 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>
@@ -122,7 +123,7 @@
 
 static void assertAxisResolution(MultiTouchInputMapper& mapper, int axis, float resolution) {
     InputDeviceInfo info;
-    mapper.populateDeviceInfo(&info);
+    mapper.populateDeviceInfo(info);
 
     const InputDeviceInfo::MotionRange* motionRange =
             info.getMotionRange(axis, AINPUT_SOURCE_TOUCHSCREEN);
@@ -131,7 +132,7 @@
 
 static void assertAxisNotPresent(MultiTouchInputMapper& mapper, int axis) {
     InputDeviceInfo info;
-    mapper.populateDeviceInfo(&info);
+    mapper.populateDeviceInfo(info);
 
     const InputDeviceInfo::MotionRange* motionRange =
             info.getMotionRange(axis, AINPUT_SOURCE_TOUCHSCREEN);
@@ -170,8 +171,9 @@
 
     std::optional<DisplayViewport> mViewport;
 public:
-    FakeInputMapper(InputDeviceContext& deviceContext, uint32_t sources)
-          : InputMapper(deviceContext),
+    FakeInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig,
+                    uint32_t sources)
+          : InputMapper(deviceContext, readerConfig),
             mSources(sources),
             mKeyboardType(AINPUT_KEYBOARD_TYPE_NONE),
             mMetaState(0),
@@ -254,23 +256,23 @@
 private:
     uint32_t getSources() const override { return mSources; }
 
-    void populateDeviceInfo(InputDeviceInfo* deviceInfo) override {
+    void populateDeviceInfo(InputDeviceInfo& deviceInfo) override {
         InputMapper::populateDeviceInfo(deviceInfo);
 
         if (mKeyboardType != AINPUT_KEYBOARD_TYPE_NONE) {
-            deviceInfo->setKeyboardType(mKeyboardType);
+            deviceInfo.setKeyboardType(mKeyboardType);
         }
     }
 
-    std::list<NotifyArgs> configure(nsecs_t, const InputReaderConfiguration* config,
-                                    uint32_t changes) override {
+    std::list<NotifyArgs> reconfigure(nsecs_t, const InputReaderConfiguration& config,
+                                      ConfigurationChanges changes) override {
         std::scoped_lock<std::mutex> lock(mLock);
         mConfigureWasCalled = true;
 
         // Find the associated viewport if exist.
         const std::optional<uint8_t> displayPort = getDeviceContext().getAssociatedDisplayPort();
-        if (displayPort && (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
-            mViewport = config->getDisplayViewportByPort(*displayPort);
+        if (displayPort && changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) {
+            mViewport = config.getDisplayViewportByPort(*displayPort);
         }
 
         mStateChangedCondition.notify_all();
@@ -602,17 +604,18 @@
         mReader->loopOnce();
         mReader->loopOnce();
         ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyInputDevicesChangedWasCalled());
         ASSERT_NO_FATAL_FAILURE(mFakeEventHub->assertQueueIsEmpty());
     }
 
     void disableDevice(int32_t deviceId) {
         mFakePolicy->addDisabledDevice(deviceId);
-        mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_ENABLED_STATE);
+        mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::ENABLED_STATE);
     }
 
     void enableDevice(int32_t deviceId) {
         mFakePolicy->removeDisabledDevice(deviceId);
-        mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_ENABLED_STATE);
+        mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::ENABLED_STATE);
     }
 
     FakeInputMapper& addDeviceWithFakeInputMapper(int32_t deviceId, int32_t eventHubId,
@@ -621,7 +624,9 @@
                                                   uint32_t sources,
                                                   const PropertyMap* configuration) {
         std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, name);
-        FakeInputMapper& mapper = device->addMapper<FakeInputMapper>(eventHubId, sources);
+        FakeInputMapper& mapper =
+                device->addMapper<FakeInputMapper>(eventHubId,
+                                                   mFakePolicy->getReaderConfiguration(), sources);
         mReader->pushNextDevice(device);
         addDevice(eventHubId, name, classes, configuration);
         return mapper;
@@ -643,14 +648,40 @@
     ASSERT_EQ(0U, inputDevices[0].getMotionRanges().size());
 }
 
+TEST_F(InputReaderTest, InputDeviceRecreatedOnSysfsNodeChanged) {
+    ASSERT_NO_FATAL_FAILURE(addDevice(1, "keyboard", InputDeviceClass::KEYBOARD, nullptr));
+    mFakeEventHub->setSysfsRootPath(1, "xyz");
+
+    // Should also have received a notification describing the new input device.
+    ASSERT_EQ(1U, mFakePolicy->getInputDevices().size());
+    InputDeviceInfo inputDevice = mFakePolicy->getInputDevices()[0];
+    ASSERT_EQ(0U, inputDevice.getLights().size());
+
+    RawLightInfo infoMonolight = {.id = 123,
+                                  .name = "mono_keyboard_backlight",
+                                  .maxBrightness = 255,
+                                  .flags = InputLightClass::BRIGHTNESS,
+                                  .path = ""};
+    mFakeEventHub->addRawLightInfo(/*rawId=*/123, std::move(infoMonolight));
+    mReader->sysfsNodeChanged("xyz");
+    mReader->loopOnce();
+
+    // Should also have received a notification describing the new recreated input device.
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+    inputDevice = mFakePolicy->getInputDevices()[0];
+    ASSERT_EQ(1U, inputDevice.getLights().size());
+}
+
 TEST_F(InputReaderTest, GetMergedInputDevices) {
     constexpr int32_t deviceId = END_RESERVED_ID + 1000;
     constexpr int32_t eventHubIds[2] = {END_RESERVED_ID, END_RESERVED_ID + 1};
     // Add two subdevices to device
     std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
     // Must add at least one mapper or the device will be ignored!
-    device->addMapper<FakeInputMapper>(eventHubIds[0], AINPUT_SOURCE_KEYBOARD);
-    device->addMapper<FakeInputMapper>(eventHubIds[1], AINPUT_SOURCE_KEYBOARD);
+    device->addMapper<FakeInputMapper>(eventHubIds[0], mFakePolicy->getReaderConfiguration(),
+                                       AINPUT_SOURCE_KEYBOARD);
+    device->addMapper<FakeInputMapper>(eventHubIds[1], mFakePolicy->getReaderConfiguration(),
+                                       AINPUT_SOURCE_KEYBOARD);
 
     // Push same device instance for next device to be added, so they'll have same identifier.
     mReader->pushNextDevice(device);
@@ -670,8 +701,10 @@
     // Add two subdevices to device
     std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
     // Must add at least one mapper or the device will be ignored!
-    device->addMapper<FakeInputMapper>(eventHubIds[0], AINPUT_SOURCE_KEYBOARD);
-    device->addMapper<FakeInputMapper>(eventHubIds[1], AINPUT_SOURCE_KEYBOARD);
+    device->addMapper<FakeInputMapper>(eventHubIds[0], mFakePolicy->getReaderConfiguration(),
+                                       AINPUT_SOURCE_KEYBOARD);
+    device->addMapper<FakeInputMapper>(eventHubIds[1], mFakePolicy->getReaderConfiguration(),
+                                       AINPUT_SOURCE_KEYBOARD);
 
     // Push same device instance for next device to be added, so they'll have same identifier.
     mReader->pushNextDevice(device);
@@ -696,7 +729,8 @@
     constexpr int32_t eventHubId = 1;
     std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
     // Must add at least one mapper or the device will be ignored!
-    device->addMapper<FakeInputMapper>(eventHubId, AINPUT_SOURCE_KEYBOARD);
+    device->addMapper<FakeInputMapper>(eventHubId, mFakePolicy->getReaderConfiguration(),
+                                       AINPUT_SOURCE_KEYBOARD);
     mReader->pushNextDevice(device);
     ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
 
@@ -942,7 +976,8 @@
     constexpr int32_t eventHubId = 1;
     std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
     // Must add at least one mapper or the device will be ignored!
-    device->addMapper<FakeInputMapper>(eventHubId, AINPUT_SOURCE_KEYBOARD);
+    device->addMapper<FakeInputMapper>(eventHubId, mFakePolicy->getReaderConfiguration(),
+                                       AINPUT_SOURCE_KEYBOARD);
     mReader->pushNextDevice(device);
     ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
 
@@ -975,7 +1010,8 @@
     constexpr int32_t eventHubId = 1;
     std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
     // Must add at least one mapper or the device will be ignored!
-    device->addMapper<FakeInputMapper>(eventHubId, AINPUT_SOURCE_KEYBOARD);
+    device->addMapper<FakeInputMapper>(eventHubId, mFakePolicy->getReaderConfiguration(),
+                                       AINPUT_SOURCE_KEYBOARD);
     mReader->pushNextDevice(device);
     ASSERT_NO_FATAL_FAILURE(addDevice(deviceId, "fake", deviceClass, nullptr));
 
@@ -991,7 +1027,8 @@
     const char* DEVICE_LOCATION = "USB1";
     std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION);
     FakeInputMapper& mapper =
-            device->addMapper<FakeInputMapper>(eventHubId, AINPUT_SOURCE_TOUCHSCREEN);
+            device->addMapper<FakeInputMapper>(eventHubId, mFakePolicy->getReaderConfiguration(),
+                                               AINPUT_SOURCE_TOUCHSCREEN);
     mReader->pushNextDevice(device);
 
     const uint8_t hdmi1 = 1;
@@ -1006,7 +1043,7 @@
     mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
                                     ui::ROTATION_0, /*isActive=*/true, "local:1", hdmi1,
                                     ViewportType::EXTERNAL);
-    mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::DISPLAY_INFO);
     mReader->loopOnce();
 
     // Add the device, and make sure all of the callbacks are triggered.
@@ -1034,8 +1071,10 @@
     constexpr int32_t eventHubIds[2] = {END_RESERVED_ID, END_RESERVED_ID + 1};
     std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
     // Must add at least one mapper or the device will be ignored!
-    device->addMapper<FakeInputMapper>(eventHubIds[0], AINPUT_SOURCE_KEYBOARD);
-    device->addMapper<FakeInputMapper>(eventHubIds[1], AINPUT_SOURCE_KEYBOARD);
+    device->addMapper<FakeInputMapper>(eventHubIds[0], mFakePolicy->getReaderConfiguration(),
+                                       AINPUT_SOURCE_KEYBOARD);
+    device->addMapper<FakeInputMapper>(eventHubIds[1], mFakePolicy->getReaderConfiguration(),
+                                       AINPUT_SOURCE_KEYBOARD);
     mReader->pushNextDevice(device);
     mReader->pushNextDevice(device);
     ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[0], "fake1", deviceClass, nullptr));
@@ -1076,9 +1115,13 @@
     // Add two subdevices to device
     std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
     FakeInputMapper& mapperDevice1 =
-            device->addMapper<FakeInputMapper>(eventHubIds[0], AINPUT_SOURCE_KEYBOARD);
+            device->addMapper<FakeInputMapper>(eventHubIds[0],
+                                               mFakePolicy->getReaderConfiguration(),
+                                               AINPUT_SOURCE_KEYBOARD);
     FakeInputMapper& mapperDevice2 =
-            device->addMapper<FakeInputMapper>(eventHubIds[1], AINPUT_SOURCE_KEYBOARD);
+            device->addMapper<FakeInputMapper>(eventHubIds[1],
+                                               mFakePolicy->getReaderConfiguration(),
+                                               AINPUT_SOURCE_KEYBOARD);
     mReader->pushNextDevice(device);
     mReader->pushNextDevice(device);
     ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[0], "fake1", deviceClass, nullptr));
@@ -1099,29 +1142,30 @@
     NotifyPointerCaptureChangedArgs args;
 
     auto request = mFakePolicy->setPointerCapture(true);
-    mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
+    mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::POINTER_CAPTURE);
     mReader->loopOnce();
     mFakeListener->assertNotifyCaptureWasCalled(&args);
     ASSERT_TRUE(args.request.enable) << "Pointer Capture should be enabled.";
     ASSERT_EQ(args.request, request) << "Pointer Capture sequence number should match.";
 
     mFakePolicy->setPointerCapture(false);
-    mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
+    mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::POINTER_CAPTURE);
     mReader->loopOnce();
     mFakeListener->assertNotifyCaptureWasCalled(&args);
     ASSERT_FALSE(args.request.enable) << "Pointer Capture should be disabled.";
 
     // Verify that the Pointer Capture state is not updated when the configuration value
     // does not change.
-    mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
+    mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::POINTER_CAPTURE);
     mReader->loopOnce();
     mFakeListener->assertNotifyCaptureWasNotCalled();
 }
 
 class FakeVibratorInputMapper : public FakeInputMapper {
 public:
-    FakeVibratorInputMapper(InputDeviceContext& deviceContext, uint32_t sources)
-          : FakeInputMapper(deviceContext, sources) {}
+    FakeVibratorInputMapper(InputDeviceContext& deviceContext,
+                            const InputReaderConfiguration& readerConfig, uint32_t sources)
+          : FakeInputMapper(deviceContext, readerConfig, sources) {}
 
     std::vector<int32_t> getVibratorIds() override { return getDeviceContext().getVibratorIds(); }
 };
@@ -1134,7 +1178,9 @@
     const char* DEVICE_LOCATION = "BLUETOOTH";
     std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION);
     FakeVibratorInputMapper& mapper =
-            device->addMapper<FakeVibratorInputMapper>(eventHubId, AINPUT_SOURCE_KEYBOARD);
+            device->addMapper<FakeVibratorInputMapper>(eventHubId,
+                                                       mFakePolicy->getReaderConfiguration(),
+                                                       AINPUT_SOURCE_KEYBOARD);
     mReader->pushNextDevice(device);
 
     ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
@@ -1299,6 +1345,7 @@
         // to the test device will show up in mReader. We wait for those input devices to
         // show up before beginning the tests.
         ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+        ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyInputDevicesChangedWasCalled());
         ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
     }
 
@@ -1480,7 +1527,7 @@
                                       ViewportType viewportType) {
         mFakePolicy->addDisplayViewport(displayId, width, height, orientation, /*isActive=*/true,
                                         uniqueId, physicalPort, viewportType);
-        mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+        mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::DISPLAY_INFO);
     }
 
     void assertReceivedMotion(int32_t action, const std::vector<Point>& points) {
@@ -1731,7 +1778,7 @@
     mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+                  WithToolType(ToolType::STYLUS))));
 
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotified(mDeviceInfo.getId()));
 
@@ -1750,7 +1797,7 @@
     mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))));
+                  WithToolType(ToolType::FINGER))));
 
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotNotified());
 
@@ -1767,7 +1814,7 @@
     mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+                  WithToolType(ToolType::STYLUS))));
 
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotified(mDeviceInfo.getId()));
 }
@@ -1830,7 +1877,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 +1891,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();
@@ -1863,12 +1910,12 @@
     TestFixture::mTouchscreen->sendSync();
     ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS),
+                  WithToolType(ToolType::STYLUS),
                   WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY),
                   WithDeviceId(touchscreenId))));
     ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS),
+                  WithToolType(ToolType::STYLUS),
                   WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY),
                   WithDeviceId(touchscreenId))));
 
@@ -1876,11 +1923,11 @@
     TestFixture::mTouchscreen->sendSync();
     ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithToolType(ToolType::STYLUS), WithButtonState(0),
                   WithDeviceId(touchscreenId))));
     ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithToolType(ToolType::STYLUS), WithButtonState(0),
                   WithDeviceId(touchscreenId))));
 
     // Release the stylus button.
@@ -1890,12 +1937,12 @@
                   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();
     auto toolTypeDevice =
-            AllOf(WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithDeviceId(touchscreenId));
+            AllOf(WithToolType(ToolType::STYLUS), WithDeviceId(touchscreenId));
 
     // Press the stylus button.
     TestFixture::mStylus->pressKey(BTN_STYLUS);
@@ -1966,7 +2013,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();
@@ -1979,7 +2026,7 @@
     TestFixture::mTouchscreen->sendSync();
     ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithToolType(ToolType::STYLUS), WithButtonState(0),
                   WithDeviceId(touchscreenId))));
 
     // Press and release a stylus button. Each change in button state also generates a MOVE event.
@@ -1989,12 +2036,12 @@
                   WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
     ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS),
+                  WithToolType(ToolType::STYLUS),
                   WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY),
                   WithDeviceId(touchscreenId))));
     ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS),
+                  WithToolType(ToolType::STYLUS),
                   WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY),
                   WithDeviceId(touchscreenId))));
 
@@ -2004,11 +2051,11 @@
                   WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
     ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithToolType(ToolType::STYLUS), WithButtonState(0),
                   WithDeviceId(touchscreenId))));
     ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithToolType(ToolType::STYLUS), WithButtonState(0),
                   WithDeviceId(touchscreenId))));
 
     // Finish the stylus gesture.
@@ -2016,14 +2063,14 @@
     TestFixture::mTouchscreen->sendSync();
     ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithToolType(ToolType::STYLUS), WithButtonState(0),
                   WithDeviceId(touchscreenId))));
 }
 
-TYPED_TEST(StylusButtonIntegrationTest, StylusButtonMotionEventsDisabled) {
+TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonMotionEventsDisabled) {
     TestFixture::mFakePolicy->setStylusButtonMotionEventsEnabled(false);
     TestFixture::mReader->requestRefreshConfiguration(
-            InputReaderConfiguration::CHANGE_STYLUS_BUTTON_REPORTING);
+            InputReaderConfiguration::Change::STYLUS_BUTTON_REPORTING);
 
     const Point centerPoint = TestFixture::mTouchscreen->getCenterPoint();
     const auto touchscreenId = TestFixture::mTouchscreenInfo.getId();
@@ -2038,7 +2085,7 @@
     TestFixture::mTouchscreen->sendSync();
     ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithToolType(ToolType::STYLUS), WithButtonState(0),
                   WithDeviceId(touchscreenId))));
 
     // Press and release a stylus button. Each change only generates a MOVE motion event.
@@ -2049,7 +2096,7 @@
                   WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
     ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithToolType(ToolType::STYLUS), WithButtonState(0),
                   WithDeviceId(touchscreenId))));
 
     TestFixture::mStylus->releaseKey(BTN_STYLUS);
@@ -2058,7 +2105,7 @@
                   WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
     ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithToolType(ToolType::STYLUS), WithButtonState(0),
                   WithDeviceId(touchscreenId))));
 
     // Finish the stylus gesture.
@@ -2066,7 +2113,7 @@
     TestFixture::mTouchscreen->sendSync();
     ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithToolType(ToolType::STYLUS), WithButtonState(0),
                   WithDeviceId(touchscreenId))));
 }
 
@@ -2077,7 +2124,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
@@ -2107,7 +2154,7 @@
     mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithToolType(ToolType::STYLUS), WithButtonState(0),
                   WithDeviceId(touchscreenId), WithPressure(100.f / RAW_PRESSURE_MAX))));
 
     // Change the pressure on the external stylus, and ensure the touchscreen generates a MOVE
@@ -2115,7 +2162,7 @@
     stylus->setPressure(200);
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithToolType(ToolType::STYLUS), WithButtonState(0),
                   WithDeviceId(touchscreenId), WithPressure(200.f / RAW_PRESSURE_MAX))));
 
     // The external stylus did not generate any events.
@@ -2123,7 +2170,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
@@ -2161,7 +2208,7 @@
     // it shows up as a finger pointer.
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithDeviceId(touchscreenId),
+                  WithToolType(ToolType::FINGER), WithDeviceId(touchscreenId),
                   WithPressure(1.f))));
 
     // Change the pressure on the external stylus. Since the pressure was not present at the start
@@ -2174,7 +2221,7 @@
     mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))));
+                  WithToolType(ToolType::FINGER))));
 
     // Start a new gesture. Since we have a valid pressure value, it shows up as a stylus.
     mDevice->sendTrackingId(FIRST_TRACKING_ID);
@@ -2183,7 +2230,7 @@
     mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithToolType(ToolType::STYLUS), WithButtonState(0),
                   WithDeviceId(touchscreenId), WithPressure(200.f / RAW_PRESSURE_MAX))));
 
     // The external stylus did not generate any events.
@@ -2191,7 +2238,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
@@ -2219,7 +2266,7 @@
             mTestListener
                     ->assertNotifyMotionWasCalled(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
                                                         WithToolType(
-                                                                AMOTION_EVENT_TOOL_TYPE_FINGER),
+                                                                ToolType::FINGER),
                                                         WithButtonState(0),
                                                         WithDeviceId(touchscreenId),
                                                         WithPressure(1.f)),
@@ -2294,7 +2341,7 @@
 TEST_F(InputDeviceTest, WhenNoMappersAreRegistered_DeviceIsIgnored) {
     // Configuration.
     InputReaderConfiguration config;
-    std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, &config, 0);
+    std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{});
 
     // Reset.
     unused += mDevice->reset(ARBITRARY_TIME);
@@ -2337,7 +2384,8 @@
     mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, "key", "value");
 
     FakeInputMapper& mapper1 =
-            mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD);
+            mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+                                                AINPUT_SOURCE_KEYBOARD);
     mapper1.setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC);
     mapper1.setMetaState(AMETA_ALT_ON);
     mapper1.addSupportedKeyCode(AKEYCODE_A);
@@ -2349,16 +2397,17 @@
     mapper1.setSwitchState(4, AKEY_STATE_DOWN);
 
     FakeInputMapper& mapper2 =
-            mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, AINPUT_SOURCE_TOUCHSCREEN);
+            mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+                                                AINPUT_SOURCE_TOUCHSCREEN);
     mapper2.setMetaState(AMETA_SHIFT_ON);
 
     InputReaderConfiguration config;
-    std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, &config, 0);
+    std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{});
 
-    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());
@@ -2430,11 +2479,13 @@
 // 1. Device is disabled if the viewport corresponding to the associated display is not found
 // 2. Device is disabled when setEnabled API is called
 TEST_F(InputDeviceTest, Configure_AssignsDisplayPort) {
-    mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, AINPUT_SOURCE_TOUCHSCREEN);
+    mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+                                        AINPUT_SOURCE_TOUCHSCREEN);
 
     // First Configuration.
     std::list<NotifyArgs> unused =
-            mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0);
+            mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+                               /*changes=*/{});
 
     // Device should be enabled by default.
     ASSERT_TRUE(mDevice->isEnabled());
@@ -2445,7 +2496,7 @@
 
     mFakePolicy->addInputPortAssociation(DEVICE_LOCATION, hdmi);
     unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                                 InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+                                 InputReaderConfiguration::Change::DISPLAY_INFO);
     // Device should be disabled because it is associated with a specific display via
     // input port <-> display port association, but the corresponding display is not found
     ASSERT_FALSE(mDevice->isEnabled());
@@ -2455,34 +2506,36 @@
                                     ui::ROTATION_0, /*isActive=*/true, UNIQUE_ID, hdmi,
                                     ViewportType::INTERNAL);
     unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                                 InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+                                 InputReaderConfiguration::Change::DISPLAY_INFO);
     ASSERT_TRUE(mDevice->isEnabled());
 
     // Device should be disabled after set disable.
     mFakePolicy->addDisabledDevice(mDevice->getId());
     unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                                 InputReaderConfiguration::CHANGE_ENABLED_STATE);
+                                 InputReaderConfiguration::Change::ENABLED_STATE);
     ASSERT_FALSE(mDevice->isEnabled());
 
     // Device should still be disabled even found the associated display.
     unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                                 InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+                                 InputReaderConfiguration::Change::DISPLAY_INFO);
     ASSERT_FALSE(mDevice->isEnabled());
 }
 
 TEST_F(InputDeviceTest, Configure_AssignsDisplayUniqueId) {
     // Device should be enabled by default.
     mFakePolicy->clearViewports();
-    mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD);
+    mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+                                        AINPUT_SOURCE_KEYBOARD);
     std::list<NotifyArgs> unused =
-            mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0);
+            mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+                               /*changes=*/{});
     ASSERT_TRUE(mDevice->isEnabled());
 
     // Device should be disabled because it is associated with a specific display, but the
     // corresponding display is not found.
     mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID);
     unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                                 InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+                                 InputReaderConfiguration::Change::DISPLAY_INFO);
     ASSERT_FALSE(mDevice->isEnabled());
 
     // Device should be enabled when a display is found.
@@ -2490,33 +2543,35 @@
                                     ui::ROTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID,
                                     NO_PORT, ViewportType::INTERNAL);
     unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                                 InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+                                 InputReaderConfiguration::Change::DISPLAY_INFO);
     ASSERT_TRUE(mDevice->isEnabled());
 
     // Device should be disabled after set disable.
     mFakePolicy->addDisabledDevice(mDevice->getId());
     unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                                 InputReaderConfiguration::CHANGE_ENABLED_STATE);
+                                 InputReaderConfiguration::Change::ENABLED_STATE);
     ASSERT_FALSE(mDevice->isEnabled());
 
     // Device should still be disabled even found the associated display.
     unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                                 InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+                                 InputReaderConfiguration::Change::DISPLAY_INFO);
     ASSERT_FALSE(mDevice->isEnabled());
 }
 
 TEST_F(InputDeviceTest, Configure_UniqueId_CorrectlyMatches) {
     mFakePolicy->clearViewports();
-    mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD);
+    mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+                                        AINPUT_SOURCE_KEYBOARD);
     std::list<NotifyArgs> unused =
-            mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0);
+            mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+                               /*changes=*/{});
 
     mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID);
     mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
                                     ui::ROTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID,
                                     NO_PORT, ViewportType::INTERNAL);
     unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                                 InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+                                 InputReaderConfiguration::Change::DISPLAY_INFO);
     ASSERT_EQ(DISPLAY_UNIQUE_ID, mDevice->getAssociatedDisplayUniqueId());
 }
 
@@ -2529,7 +2584,7 @@
     mFakeEventHub->addDevice(TEST_EVENTHUB_ID, "Test EventHub device", InputDeviceClass::BATTERY);
 
     InputDevice device(mReader->getContext(), /*id=*/1, /*generation=*/2, /*identifier=*/{});
-    device.addEventHubDevice(TEST_EVENTHUB_ID, /*populateMappers=*/true);
+    device.addEventHubDevice(TEST_EVENTHUB_ID, mFakePolicy->getReaderConfiguration());
     device.removeEventHubDevice(TEST_EVENTHUB_ID);
     std::string dumpStr, eventHubDevStr;
     device.dump(dumpStr, eventHubDevStr);
@@ -2548,13 +2603,13 @@
 };
 
 TEST_F(SwitchInputMapperTest, GetSources) {
-    SwitchInputMapper& mapper = addMapperAndConfigure<SwitchInputMapper>();
+    SwitchInputMapper& mapper = constructAndAddMapper<SwitchInputMapper>();
 
     ASSERT_EQ(uint32_t(AINPUT_SOURCE_SWITCH), mapper.getSources());
 }
 
 TEST_F(SwitchInputMapperTest, GetSwitchState) {
-    SwitchInputMapper& mapper = addMapperAndConfigure<SwitchInputMapper>();
+    SwitchInputMapper& mapper = constructAndAddMapper<SwitchInputMapper>();
 
     mFakeEventHub->setSwitchState(EVENTHUB_ID, SW_LID, 1);
     ASSERT_EQ(1, mapper.getSwitchState(AINPUT_SOURCE_ANY, SW_LID));
@@ -2564,7 +2619,7 @@
 }
 
 TEST_F(SwitchInputMapperTest, Process) {
-    SwitchInputMapper& mapper = addMapperAndConfigure<SwitchInputMapper>();
+    SwitchInputMapper& mapper = constructAndAddMapper<SwitchInputMapper>();
     std::list<NotifyArgs> out;
     out = process(mapper, ARBITRARY_TIME, READ_TIME, EV_SW, SW_LID, 1);
     ASSERT_TRUE(out.empty());
@@ -2590,13 +2645,13 @@
 };
 
 TEST_F(VibratorInputMapperTest, GetSources) {
-    VibratorInputMapper& mapper = addMapperAndConfigure<VibratorInputMapper>();
+    VibratorInputMapper& mapper = constructAndAddMapper<VibratorInputMapper>();
 
     ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, mapper.getSources());
 }
 
 TEST_F(VibratorInputMapperTest, GetVibratorIds) {
-    VibratorInputMapper& mapper = addMapperAndConfigure<VibratorInputMapper>();
+    VibratorInputMapper& mapper = constructAndAddMapper<VibratorInputMapper>();
 
     ASSERT_EQ(mapper.getVibratorIds().size(), 2U);
 }
@@ -2604,7 +2659,7 @@
 TEST_F(VibratorInputMapperTest, Vibrate) {
     constexpr uint8_t DEFAULT_AMPLITUDE = 192;
     constexpr int32_t VIBRATION_TOKEN = 100;
-    VibratorInputMapper& mapper = addMapperAndConfigure<VibratorInputMapper>();
+    VibratorInputMapper& mapper = constructAndAddMapper<VibratorInputMapper>();
 
     VibrationElement pattern(2);
     VibrationSequence sequence(2);
@@ -2729,7 +2784,7 @@
 }
 
 TEST_F(SensorInputMapperTest, GetSources) {
-    SensorInputMapper& mapper = addMapperAndConfigure<SensorInputMapper>();
+    SensorInputMapper& mapper = constructAndAddMapper<SensorInputMapper>();
 
     ASSERT_EQ(static_cast<uint32_t>(AINPUT_SOURCE_SENSOR), mapper.getSources());
 }
@@ -2737,7 +2792,7 @@
 TEST_F(SensorInputMapperTest, ProcessAccelerometerSensor) {
     setAccelProperties();
     prepareAccelAxes();
-    SensorInputMapper& mapper = addMapperAndConfigure<SensorInputMapper>();
+    SensorInputMapper& mapper = constructAndAddMapper<SensorInputMapper>();
 
     ASSERT_TRUE(mapper.enableSensor(InputDeviceSensorType::ACCELEROMETER,
                                     std::chrono::microseconds(10000),
@@ -2767,7 +2822,7 @@
 TEST_F(SensorInputMapperTest, ProcessGyroscopeSensor) {
     setGyroProperties();
     prepareGyroAxes();
-    SensorInputMapper& mapper = addMapperAndConfigure<SensorInputMapper>();
+    SensorInputMapper& mapper = constructAndAddMapper<SensorInputMapper>();
 
     ASSERT_TRUE(mapper.enableSensor(InputDeviceSensorType::GYROSCOPE,
                                     std::chrono::microseconds(10000),
@@ -3365,11 +3420,13 @@
                                                        AINPUT_KEYBOARD_TYPE_ALPHABETIC);
 
     KeyboardInputMapper& mapper2 =
-            device2->addMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD,
+            device2->addMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
+                                                    mFakePolicy->getReaderConfiguration(),
+                                                    AINPUT_SOURCE_KEYBOARD,
                                                     AINPUT_KEYBOARD_TYPE_ALPHABETIC);
     std::list<NotifyArgs> unused =
             device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                               /*changes=*/0);
+                               /*changes=*/{});
     unused += device2->reset(ARBITRARY_TIME);
 
     // Prepared displays and associated info.
@@ -3382,7 +3439,7 @@
 
     // No associated display viewport found, should disable the device.
     unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                                 InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+                                 InputReaderConfiguration::Change::DISPLAY_INFO);
     ASSERT_FALSE(device2->isEnabled());
 
     // Prepare second display.
@@ -3393,7 +3450,7 @@
                                  SECONDARY_UNIQUE_ID, hdmi2, ViewportType::EXTERNAL);
     // Default device will reconfigure above, need additional reconfiguration for another device.
     unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                                 InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+                                 InputReaderConfiguration::Change::DISPLAY_INFO);
 
     // Device should be enabled after the associated display is found.
     ASSERT_TRUE(mDevice->isEnabled());
@@ -3475,11 +3532,13 @@
     mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
 
     KeyboardInputMapper& mapper2 =
-            device2->addMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD,
+            device2->addMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
+                                                    mFakePolicy->getReaderConfiguration(),
+                                                    AINPUT_SOURCE_KEYBOARD,
                                                     AINPUT_KEYBOARD_TYPE_ALPHABETIC);
     std::list<NotifyArgs> unused =
             device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                               /*changes=*/0);
+                               /*changes=*/{});
     unused += device2->reset(ARBITRARY_TIME);
 
     ASSERT_TRUE(mFakeEventHub->getLedState(SECOND_EVENTHUB_ID, LED_CAPSL));
@@ -3536,11 +3595,13 @@
     mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
 
     KeyboardInputMapper& mapper2 =
-            device2->addMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD,
+            device2->addMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
+                                                    mFakePolicy->getReaderConfiguration(),
+                                                    AINPUT_SOURCE_KEYBOARD,
                                                     AINPUT_KEYBOARD_TYPE_ALPHABETIC);
     std::list<NotifyArgs> unused =
             device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                               /*changes=*/0);
+                               /*changes=*/{});
     unused += device2->reset(ARBITRARY_TIME);
 
     // Initial metastate is AMETA_NONE.
@@ -3609,7 +3670,7 @@
 
     // Disable device, it should synthesize cancellation events for down events.
     mFakePolicy->addDisabledDevice(DEVICE_ID);
-    configureDevice(InputReaderConfiguration::CHANGE_ENABLED_STATE);
+    configureDevice(InputReaderConfiguration::Change::ENABLED_STATE);
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
@@ -3619,21 +3680,32 @@
 }
 
 TEST_F(KeyboardInputMapperTest, Configure_AssignKeyboardLayoutInfo) {
-    mDevice->addMapper<KeyboardInputMapper>(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD,
+    mDevice->addMapper<KeyboardInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+                                            AINPUT_SOURCE_KEYBOARD,
                                             AINPUT_KEYBOARD_TYPE_ALPHABETIC);
     std::list<NotifyArgs> unused =
-            mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0);
+            mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+                               /*changes=*/{});
 
+    uint32_t generation = mReader->getContext()->getGeneration();
     mFakePolicy->addKeyboardLayoutAssociation(DEVICE_LOCATION, DEVICE_KEYBOARD_LAYOUT_INFO);
 
     unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                                 InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUT_ASSOCIATION);
+                                 InputReaderConfiguration::Change::KEYBOARD_LAYOUT_ASSOCIATION);
 
     InputDeviceInfo deviceInfo = mDevice->getDeviceInfo();
     ASSERT_EQ(DEVICE_KEYBOARD_LAYOUT_INFO.languageTag,
               deviceInfo.getKeyboardLayoutInfo()->languageTag);
     ASSERT_EQ(DEVICE_KEYBOARD_LAYOUT_INFO.layoutType,
               deviceInfo.getKeyboardLayoutInfo()->layoutType);
+    ASSERT_TRUE(mReader->getContext()->getGeneration() != generation);
+
+    // Call change layout association with the same values: Generation shouldn't change
+    generation = mReader->getContext()->getGeneration();
+    mFakePolicy->addKeyboardLayoutAssociation(DEVICE_LOCATION, DEVICE_KEYBOARD_LAYOUT_INFO);
+    unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+                                 InputReaderConfiguration::Change::KEYBOARD_LAYOUT_ASSOCIATION);
+    ASSERT_TRUE(mReader->getContext()->getGeneration() == generation);
 }
 
 TEST_F(KeyboardInputMapperTest, LayoutInfoCorrectlyMapped) {
@@ -3644,7 +3716,7 @@
     addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
                                                AINPUT_KEYBOARD_TYPE_ALPHABETIC);
     InputReaderConfiguration config;
-    std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, &config, 0);
+    std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{});
 
     ASSERT_EQ("en", mDevice->getDeviceInfo().getKeyboardLayoutInfo()->languageTag);
     ASSERT_EQ("extended", mDevice->getDeviceInfo().getKeyboardLayoutInfo()->layoutType);
@@ -3658,8 +3730,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 +3753,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));
@@ -3790,24 +3862,24 @@
 
 TEST_F(CursorInputMapperTest, WhenModeIsPointer_GetSources_ReturnsMouse) {
     addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
 }
 
 TEST_F(CursorInputMapperTest, WhenModeIsNavigation_GetSources_ReturnsTrackball) {
     addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, mapper.getSources());
 }
 
 TEST_F(CursorInputMapperTest, WhenModeIsPointer_PopulateDeviceInfo_ReturnsRangeFromPointerController) {
     addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     InputDeviceInfo info;
-    mapper.populateDeviceInfo(&info);
+    mapper.populateDeviceInfo(info);
 
     // Initially there may not be a valid motion range.
     ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE));
@@ -3819,7 +3891,7 @@
     mFakePointerController->setBounds(1, 2, 800 - 1, 480 - 1);
 
     InputDeviceInfo info2;
-    mapper.populateDeviceInfo(&info2);
+    mapper.populateDeviceInfo(info2);
 
     ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2,
             AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE,
@@ -3834,10 +3906,10 @@
 
 TEST_F(CursorInputMapperTest, WhenModeIsNavigation_PopulateDeviceInfo_ReturnsScaledRange) {
     addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     InputDeviceInfo info;
-    mapper.populateDeviceInfo(&info);
+    mapper.populateDeviceInfo(info);
 
     ASSERT_NO_FATAL_FAILURE(assertMotionRange(info,
             AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_TRACKBALL,
@@ -3852,7 +3924,7 @@
 
 TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaState) {
     addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
 
@@ -3874,7 +3946,7 @@
     ASSERT_EQ(0, args.edgeFlags);
     ASSERT_EQ(uint32_t(1), args.pointerCount);
     ASSERT_EQ(0, args.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f));
     ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
     ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
@@ -3892,7 +3964,7 @@
     ASSERT_EQ(0, args.edgeFlags);
     ASSERT_EQ(uint32_t(1), args.pointerCount);
     ASSERT_EQ(0, args.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f));
     ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
     ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
@@ -3913,7 +3985,7 @@
     ASSERT_EQ(0, args.edgeFlags);
     ASSERT_EQ(uint32_t(1), args.pointerCount);
     ASSERT_EQ(0, args.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
     ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
     ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
@@ -3931,7 +4003,7 @@
     ASSERT_EQ(0, args.edgeFlags);
     ASSERT_EQ(uint32_t(1), args.pointerCount);
     ASSERT_EQ(0, args.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
     ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
     ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
@@ -3940,7 +4012,7 @@
 
 TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentXYUpdates) {
     addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     NotifyMotionArgs args;
 
@@ -3964,7 +4036,7 @@
 
 TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentButtonUpdates) {
     addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     NotifyMotionArgs args;
 
@@ -3993,7 +4065,7 @@
 
 TEST_F(CursorInputMapperTest, Process_ShouldHandleCombinedXYAndButtonUpdates) {
     addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     NotifyMotionArgs args;
 
@@ -4042,7 +4114,7 @@
     // InputReader works in the un-rotated coordinate space, so orientation-aware devices do not
     // need to be rotated.
     addConfigurationProperty("cursor.orientationAware", "1");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     prepareDisplay(ui::ROTATION_90);
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  0,  1));
@@ -4060,7 +4132,7 @@
     addConfigurationProperty("cursor.mode", "navigation");
     // Since InputReader works in the un-rotated coordinate space, only devices that are not
     // orientation-aware are affected by display rotation.
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     clearViewports();
     prepareDisplay(ui::ROTATION_0);
@@ -4109,11 +4181,10 @@
 
 TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) {
     addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
     mFakePointerController->setPosition(100, 200);
-    mFakePointerController->setButtonState(0);
 
     NotifyMotionArgs motionArgs;
     NotifyKeyArgs keyArgs;
@@ -4124,14 +4195,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 +4209,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 +4231,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 +4253,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 +4267,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 +4274,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 +4294,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 +4308,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 +4331,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 +4345,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 +4368,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 +4382,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 +4405,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 +4419,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));
 
@@ -4396,11 +4435,10 @@
 
 TEST_F(CursorInputMapperTest, Process_WhenModeIsPointer_ShouldMoveThePointerAround) {
     addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
     mFakePointerController->setPosition(100, 200);
-    mFakePointerController->setButtonState(0);
 
     NotifyMotionArgs args;
 
@@ -4418,7 +4456,7 @@
 TEST_F(CursorInputMapperTest, Process_PointerCapture) {
     addConfigurationProperty("cursor.mode", "pointer");
     mFakePolicy->setPointerCapture(true);
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     NotifyDeviceResetArgs resetArgs;
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
@@ -4427,7 +4465,6 @@
 
     mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
     mFakePointerController->setPosition(100, 200);
-    mFakePointerController->setButtonState(0);
 
     NotifyMotionArgs args;
 
@@ -4485,7 +4522,7 @@
     // and events are generated the usual way.
     const uint32_t generation = mReader->getContext()->getGeneration();
     mFakePolicy->setPointerCapture(false);
-    configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
+    configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
     ASSERT_TRUE(mReader->getContext()->getGeneration() != generation);
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
@@ -4511,7 +4548,7 @@
     const VelocityControlParameters testParams(/*scale=*/5.f, /*low threshold=*/0.f,
                                                /*high threshold=*/100.f, /*acceleration=*/10.f);
     mFakePolicy->setVelocityControlParams(testParams);
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     NotifyDeviceResetArgs resetArgs;
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
@@ -4534,7 +4571,7 @@
 
     // Enable Pointer Capture
     mFakePolicy->setPointerCapture(true);
-    configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
+    configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
     NotifyPointerCaptureChangedArgs captureArgs;
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs));
     ASSERT_TRUE(captureArgs.request.enable);
@@ -4552,7 +4589,7 @@
 
 TEST_F(CursorInputMapperTest, PointerCaptureDisablesOrientationChanges) {
     addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     NotifyDeviceResetArgs resetArgs;
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
@@ -4576,7 +4613,7 @@
 
     // Enable Pointer Capture.
     mFakePolicy->setPointerCapture(true);
-    configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
+    configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
     NotifyPointerCaptureChangedArgs captureArgs;
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs));
     ASSERT_TRUE(captureArgs.request.enable);
@@ -4593,7 +4630,7 @@
 }
 
 TEST_F(CursorInputMapperTest, ConfigureDisplayId_NoAssociatedViewport) {
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     // Set up the default display.
     prepareDisplay(ui::ROTATION_90);
@@ -4602,11 +4639,10 @@
     // The InputDevice is not associated with any display.
     prepareSecondaryDisplay();
     mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
-    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
 
     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);
@@ -4620,7 +4656,7 @@
 }
 
 TEST_F(CursorInputMapperTest, ConfigureDisplayId_WithAssociatedViewport) {
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     // Set up the default display.
     prepareDisplay(ui::ROTATION_90);
@@ -4630,11 +4666,10 @@
     prepareSecondaryDisplay();
     mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
     mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID);
-    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
 
     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);
@@ -4647,7 +4682,7 @@
 }
 
 TEST_F(CursorInputMapperTest, ConfigureDisplayId_IgnoresEventsForMismatchedPointerDisplay) {
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     // Set up the default display as the display on which the pointer should be shown.
     prepareDisplay(ui::ROTATION_90);
@@ -4656,7 +4691,7 @@
     // Associate the InputDevice with the secondary display.
     prepareSecondaryDisplay();
     mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID);
-    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
 
     // The mapper should not generate any events because it is associated with a display that is
     // different from the pointer display.
@@ -4680,7 +4715,7 @@
 
 TEST_F(BluetoothCursorInputMapperTest, TimestampSmoothening) {
     addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     nsecs_t kernelEventTime = ARBITRARY_TIME;
     nsecs_t expectedEventTime = ARBITRARY_TIME;
@@ -4707,7 +4742,7 @@
 
 TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningIsCapped) {
     addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     nsecs_t expectedEventTime = ARBITRARY_TIME;
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
@@ -4744,7 +4779,7 @@
 
 TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningNotUsed) {
     addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     nsecs_t kernelEventTime = ARBITRARY_TIME;
     nsecs_t expectedEventTime = ARBITRARY_TIME;
@@ -5034,7 +5069,7 @@
 TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndNotACursor_ReturnsPointer) {
     prepareButtons();
     prepareAxes(POSITION);
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
 }
@@ -5043,7 +5078,7 @@
     prepareButtons();
     prepareAxes(POSITION);
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources());
 }
@@ -5054,7 +5089,7 @@
     prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     // Unknown key.
     ASSERT_EQ(AKEY_STATE_UNKNOWN, mapper.getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A));
@@ -5082,7 +5117,7 @@
     prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     // Unknown key.
     ASSERT_EQ(AKEY_STATE_UNKNOWN, mapper.getScanCodeState(AINPUT_SOURCE_ANY, KEY_A));
@@ -5110,7 +5145,7 @@
     prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     uint8_t flags[2] = { 0, 0 };
     ASSERT_TRUE(
@@ -5125,7 +5160,7 @@
     prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
 
@@ -5175,7 +5210,7 @@
     prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
 
@@ -5231,7 +5266,7 @@
     ASSERT_EQ(0, motionArgs.edgeFlags);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
@@ -5255,7 +5290,7 @@
     ASSERT_EQ(0, motionArgs.edgeFlags);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
@@ -5278,7 +5313,7 @@
     ASSERT_EQ(0, motionArgs.edgeFlags);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
@@ -5296,7 +5331,7 @@
     prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
 
@@ -5328,7 +5363,7 @@
     ASSERT_EQ(0, motionArgs.edgeFlags);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
@@ -5351,7 +5386,7 @@
     ASSERT_EQ(0, motionArgs.edgeFlags);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
@@ -5371,7 +5406,7 @@
     prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
 
@@ -5396,7 +5431,7 @@
     ASSERT_EQ(0, motionArgs.edgeFlags);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x, VIRTUAL_DISPLAY_WIDTH), toDisplayY(y, VIRTUAL_DISPLAY_HEIGHT),
             1, 0, 0, 0, 0, 0, 0, 0));
@@ -5423,7 +5458,7 @@
     ASSERT_EQ(0, motionArgs.edgeFlags);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x, VIRTUAL_DISPLAY_WIDTH), toDisplayY(y, VIRTUAL_DISPLAY_HEIGHT),
             1, 0, 0, 0, 0, 0, 0, 0));
@@ -5448,7 +5483,7 @@
     ASSERT_EQ(0, motionArgs.edgeFlags);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x, VIRTUAL_DISPLAY_WIDTH), toDisplayY(y, VIRTUAL_DISPLAY_HEIGHT),
             1, 0, 0, 0, 0, 0, 0, 0));
@@ -5467,7 +5502,7 @@
     prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
 
@@ -5491,7 +5526,7 @@
     ASSERT_EQ(0, motionArgs.edgeFlags);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
@@ -5516,7 +5551,7 @@
     ASSERT_EQ(0, motionArgs.edgeFlags);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
@@ -5539,7 +5574,7 @@
     ASSERT_EQ(0, motionArgs.edgeFlags);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
@@ -5557,7 +5592,7 @@
     prepareAxes(POSITION);
     // InputReader works in the un-rotated coordinate space, so orientation-aware devices do not
     // need to be rotated. Touchscreens are orientation-aware by default.
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     NotifyMotionArgs args;
 
@@ -5582,7 +5617,7 @@
     // Since InputReader works in the un-rotated coordinate space, only devices that are not
     // orientation-aware are affected by display rotation.
     addConfigurationProperty("touch.orientationAware", "0");
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     NotifyMotionArgs args;
 
@@ -5651,7 +5686,7 @@
     addConfigurationProperty("touch.orientation", "ORIENTATION_0");
     clearViewports();
     prepareDisplay(ui::ROTATION_0);
-    auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    auto& mapper = constructAndAddMapper<SingleTouchInputMapper>();
     NotifyMotionArgs args;
 
     // Orientation 0.
@@ -5675,7 +5710,7 @@
     addConfigurationProperty("touch.orientation", "ORIENTATION_90");
     clearViewports();
     prepareDisplay(ui::ROTATION_0);
-    auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    auto& mapper = constructAndAddMapper<SingleTouchInputMapper>();
     NotifyMotionArgs args;
 
     // Orientation 90.
@@ -5699,7 +5734,7 @@
     addConfigurationProperty("touch.orientation", "ORIENTATION_180");
     clearViewports();
     prepareDisplay(ui::ROTATION_0);
-    auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    auto& mapper = constructAndAddMapper<SingleTouchInputMapper>();
     NotifyMotionArgs args;
 
     // Orientation 180.
@@ -5723,7 +5758,7 @@
     addConfigurationProperty("touch.orientation", "ORIENTATION_270");
     clearViewports();
     prepareDisplay(ui::ROTATION_0);
-    auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    auto& mapper = constructAndAddMapper<SingleTouchInputMapper>();
     NotifyMotionArgs args;
 
     // Orientation 270.
@@ -5747,7 +5782,7 @@
     // orientation-aware are affected by display rotation.
     addConfigurationProperty("touch.orientationAware", "0");
     addConfigurationProperty("touch.orientation", "ORIENTATION_90");
-    auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    auto& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     NotifyMotionArgs args;
 
@@ -5814,7 +5849,7 @@
     prepareAxes(POSITION);
     addConfigurationProperty("touch.orientationAware", "1");
     prepareDisplay(ui::ROTATION_0);
-    auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    auto& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     // Set a physical frame in the display viewport.
     auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
@@ -5823,7 +5858,7 @@
     viewport->physicalRight = 30;
     viewport->physicalBottom = 610;
     mFakePolicy->updateViewport(*viewport);
-    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
 
     // Start the touch.
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_TOUCH, 1);
@@ -5868,7 +5903,7 @@
     prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION | PRESSURE | TOOL | DISTANCE | TILT);
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     // These calculations are based on the input device calibration documentation.
     int32_t rawX = 100;
@@ -5913,7 +5948,7 @@
     prepareLocationCalibration();
     prepareButtons();
     prepareAxes(POSITION);
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     int32_t rawX = 100;
     int32_t rawY = 200;
@@ -5935,7 +5970,7 @@
     prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION);
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     NotifyMotionArgs motionArgs;
     NotifyKeyArgs keyArgs;
@@ -6178,7 +6213,7 @@
     prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION);
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     NotifyMotionArgs motionArgs;
 
@@ -6187,14 +6222,14 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 
     // eraser
     processKey(mapper, BTN_TOOL_RUBBER, 1);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_ERASER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::ERASER, motionArgs.pointerProperties[0].toolType);
 
     // stylus
     processKey(mapper, BTN_TOOL_RUBBER, 0);
@@ -6202,7 +6237,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType);
 
     // brush
     processKey(mapper, BTN_TOOL_PEN, 0);
@@ -6210,7 +6245,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType);
 
     // pencil
     processKey(mapper, BTN_TOOL_BRUSH, 0);
@@ -6218,7 +6253,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType);
 
     // air-brush
     processKey(mapper, BTN_TOOL_PENCIL, 0);
@@ -6226,7 +6261,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType);
 
     // mouse
     processKey(mapper, BTN_TOOL_AIRBRUSH, 0);
@@ -6234,7 +6269,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::MOUSE, motionArgs.pointerProperties[0].toolType);
 
     // lens
     processKey(mapper, BTN_TOOL_MOUSE, 0);
@@ -6242,7 +6277,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::MOUSE, motionArgs.pointerProperties[0].toolType);
 
     // double-tap
     processKey(mapper, BTN_TOOL_LENS, 0);
@@ -6250,7 +6285,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 
     // triple-tap
     processKey(mapper, BTN_TOOL_DOUBLETAP, 0);
@@ -6258,7 +6293,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 
     // quad-tap
     processKey(mapper, BTN_TOOL_TRIPLETAP, 0);
@@ -6266,7 +6301,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 
     // finger
     processKey(mapper, BTN_TOOL_QUADTAP, 0);
@@ -6274,28 +6309,28 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 
     // stylus trumps finger
     processKey(mapper, BTN_TOOL_PEN, 1);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType);
 
     // eraser trumps stylus
     processKey(mapper, BTN_TOOL_RUBBER, 1);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_ERASER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::ERASER, motionArgs.pointerProperties[0].toolType);
 
     // mouse trumps eraser
     processKey(mapper, BTN_TOOL_MOUSE, 1);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::MOUSE, motionArgs.pointerProperties[0].toolType);
 
     // back to default tool type
     processKey(mapper, BTN_TOOL_MOUSE, 0);
@@ -6305,7 +6340,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 }
 
 TEST_F(SingleTouchInputMapperTest, Process_WhenBtnTouchPresent_HoversIfItsValueIsZero) {
@@ -6314,7 +6349,7 @@
     prepareButtons();
     prepareAxes(POSITION);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_FINGER, 0, AKEYCODE_UNKNOWN, 0);
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     NotifyMotionArgs motionArgs;
 
@@ -6385,7 +6420,7 @@
     prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION | PRESSURE);
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     NotifyMotionArgs motionArgs;
 
@@ -6456,7 +6491,7 @@
     prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION | PRESSURE);
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     // Touch down.
     processDown(mapper, 100, 200);
@@ -6478,7 +6513,7 @@
     prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION | PRESSURE);
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     // Set the initial state for the touch pointer.
     mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_X, 100);
@@ -6506,7 +6541,7 @@
     prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION);
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
     NotifyMotionArgs motionArgs;
 
     // Down.
@@ -6534,7 +6569,7 @@
     prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION);
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
     NotifyMotionArgs motionArgs;
 
@@ -6548,7 +6583,7 @@
     auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
     viewport->isActive = false;
     mFakePolicy->updateViewport(*viewport);
-    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
 
     // We should receive a cancel event for the ongoing gesture.
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
@@ -6573,7 +6608,7 @@
     // Make the viewport active again. The device should resume processing events.
     viewport->isActive = true;
     mFakePolicy->updateViewport(*viewport);
-    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
 
     // The device is reset because it changes back to direct mode, without generating any events.
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
@@ -6594,7 +6629,7 @@
     prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION);
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
 
     // Press a stylus button.
@@ -6635,7 +6670,7 @@
 
     mFakePolicy->setStylusButtonMotionEventsEnabled(false);
 
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
 
     // Press a stylus button.
@@ -6672,7 +6707,7 @@
     prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION);
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
 
     ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mapper.getSources());
@@ -6688,14 +6723,14 @@
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
     mFakePolicy->setPointerController(fakePointerController);
     mFakePolicy->setStylusPointerIconEnabled(true);
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     processKey(mapper, BTN_TOOL_PEN, 1);
     processMove(mapper, 100, 200);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS),
+                  WithToolType(ToolType::STYLUS),
                   WithPointerCoords(0, toDisplayX(100), toDisplayY(200)))));
     ASSERT_TRUE(fakePointerController->isPointerShown());
     ASSERT_NO_FATAL_FAILURE(
@@ -6712,14 +6747,14 @@
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
     mFakePolicy->setPointerController(fakePointerController);
     mFakePolicy->setStylusPointerIconEnabled(false);
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     processKey(mapper, BTN_TOOL_PEN, 1);
     processMove(mapper, 100, 200);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS),
+                  WithToolType(ToolType::STYLUS),
                   WithPointerCoords(0, toDisplayX(100), toDisplayY(200)))));
     ASSERT_FALSE(fakePointerController->isPointerShown());
 }
@@ -6730,7 +6765,7 @@
     prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION);
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     // Ensure that the device is created as a touchscreen, not touch navigation.
     ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources());
@@ -6741,12 +6776,60 @@
     // Send update to the mapper.
     std::list<NotifyArgs> unused2 =
             mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                               InputReaderConfiguration::CHANGE_DEVICE_TYPE /*changes*/);
+                               InputReaderConfiguration::Change::DEVICE_TYPE /*changes*/);
 
     // Check whether device type update was successful.
     ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mDevice->getSources());
 }
 
+TEST_F(SingleTouchInputMapperTest, HoverEventsOutsidePhysicalFrameAreIgnored) {
+    // Initialize the device without setting device source to touch navigation.
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(ui::ROTATION_0);
+    prepareButtons();
+    prepareAxes(POSITION);
+    mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
+
+    // Set a physical frame in the display viewport.
+    auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
+    viewport->physicalLeft = 0;
+    viewport->physicalTop = 0;
+    viewport->physicalRight = DISPLAY_WIDTH / 2;
+    viewport->physicalBottom = DISPLAY_HEIGHT / 2;
+    mFakePolicy->updateViewport(*viewport);
+    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
+
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
+
+    // Hovering inside the physical frame produces events.
+    processKey(mapper, BTN_TOOL_PEN, 1);
+    processMove(mapper, RAW_X_MIN + 1, RAW_Y_MIN + 1);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)));
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)));
+
+    // Leaving the physical frame ends the hovering gesture.
+    processMove(mapper, RAW_X_MAX - 1, RAW_Y_MAX - 1);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)));
+
+    // Moving outside the physical frame does not produce events.
+    processMove(mapper, RAW_X_MAX - 2, RAW_Y_MAX - 2);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+
+    // Re-entering the physical frame produces events.
+    processMove(mapper, RAW_X_MIN, RAW_Y_MIN);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)));
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)));
+}
+
 // --- TouchDisplayProjectionTest ---
 
 class TouchDisplayProjectionTest : public SingleTouchInputMapperTest {
@@ -6803,7 +6886,7 @@
         v.uniqueId = UNIQUE_ID;
         v.type = ViewportType::INTERNAL;
         mFakePolicy->updateViewport(v);
-        configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+        configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
     }
 
     void assertReceivedMove(const Point& point) {
@@ -6822,7 +6905,7 @@
 
     prepareButtons();
     prepareAxes(POSITION);
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     NotifyMotionArgs motionArgs;
 
@@ -6857,7 +6940,7 @@
 
     prepareButtons();
     prepareAxes(POSITION);
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     NotifyMotionArgs motionArgs;
 
@@ -6927,9 +7010,11 @@
         // 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,
-                                       0, 0);
+                                       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,
-                                       0, 0);
+                                       PRECISION_RAW_Y_FLAT, PRECISION_RAW_Y_FUZZ,
+                                       PRECISION_RAW_Y_RES);
     }
 
     static const int32_t PRECISION_RAW_X_MIN = TouchInputMapperTest::RAW_X_MIN;
@@ -6937,6 +7022,15 @@
     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;
 };
 
@@ -6975,7 +7069,7 @@
     addConfigurationProperty("touch.orientation", ftl::enum_string(touchscreenOrientation).c_str());
     prepareDisplay(ui::ROTATION_0);
 
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<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
@@ -7027,7 +7121,7 @@
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(displayRotation);
 
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     const auto& expectedPoints = kMappedCorners.at(displayRotation);
 
@@ -7065,7 +7159,7 @@
     addConfigurationProperty("touch.deviceType", "touchScreen");
     addConfigurationProperty("touch.orientation", "ORIENTATION_270");
 
-    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
     // Ori 270, so width and height swapped
     const Rect physicalFrame{0, 0, DISPLAY_HEIGHT, DISPLAY_WIDTH};
@@ -7094,6 +7188,41 @@
     }
 }
 
+TEST_P(TouchscreenPrecisionTestsFixture, MotionRangesAreOrientedInRotatedDisplay) {
+    const ui::Rotation displayRotation = GetParam();
+
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(displayRotation);
+
+    __attribute__((unused)) SingleTouchInputMapper& mapper =
+            constructAndAddMapper<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,
@@ -7111,13 +7240,13 @@
         prepareDisplay(ui::ROTATION_0);
         prepareButtons();
         prepareAxes(POSITION);
-        auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+        auto& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
         mStylusState.when = ARBITRARY_TIME;
         mStylusState.pressure = 0.f;
-        mStylusState.toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+        mStylusState.toolType = ToolType::STYLUS;
         mReader->getContext()->setExternalStylusDevices({mExternalStylusDeviceInfo});
-        configureDevice(InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE);
+        configureDevice(InputReaderConfiguration::Change::EXTERNAL_STYLUS_PRESENCE);
         processExternalStylusState(mapper);
         return mapper;
     }
@@ -7139,7 +7268,7 @@
 
     void testStartFusedStylusGesture(SingleTouchInputMapper& mapper) {
         auto toolTypeSource =
-                AllOf(WithSource(EXPECTED_SOURCE), WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS));
+                AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS));
 
         // The first pointer is withheld.
         processDown(mapper, 100, 200);
@@ -7174,7 +7303,7 @@
         processSync(mapper);
         ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(EXPECTED_SOURCE),
-                      WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+                      WithToolType(ToolType::STYLUS))));
 
         mStylusState.pressure = 0.f;
         processExternalStylusState(mapper);
@@ -7184,7 +7313,7 @@
 
     void testUnsuccessfulFusionGesture(SingleTouchInputMapper& mapper) {
         auto toolTypeSource =
-                AllOf(WithSource(EXPECTED_SOURCE), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER));
+                AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::FINGER));
 
         // The first pointer is withheld when an external stylus is connected,
         // and a timeout is requested.
@@ -7242,7 +7371,7 @@
 TEST_F(ExternalStylusFusionTest, SuccessfulFusion_PressureFirst) {
     SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
     auto toolTypeSource =
-            AllOf(WithSource(EXPECTED_SOURCE), WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS));
+            AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS));
 
     // The external stylus reports pressure first. It is ignored for now.
     mStylusState.pressure = 1.f;
@@ -7285,7 +7414,7 @@
 TEST_F(ExternalStylusFusionTest, FusedPointerReportsPressureChanges) {
     SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
     auto toolTypeSource =
-            AllOf(WithSource(EXPECTED_SOURCE), WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS));
+            AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS));
 
     mStylusState.pressure = 0.8f;
     processExternalStylusState(mapper);
@@ -7347,7 +7476,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(EXPECTED_SOURCE),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+                  WithToolType(ToolType::STYLUS))));
 
     ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
@@ -7358,17 +7487,17 @@
     auto source = WithSource(EXPECTED_SOURCE);
 
     mStylusState.pressure = 1.f;
-    mStylusState.toolType = AMOTION_EVENT_TOOL_TYPE_ERASER;
+    mStylusState.toolType = ToolType::ERASER;
     processExternalStylusState(mapper);
     processDown(mapper, 100, 200);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             AllOf(source, WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_ERASER))));
+                  WithToolType(ToolType::ERASER))));
     ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
 
     // The external stylus reports a tool change. We wait for some time for a touch event.
-    mStylusState.toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    mStylusState.toolType = ToolType::STYLUS;
     processExternalStylusState(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
     ASSERT_NO_FATAL_FAILURE(
@@ -7379,11 +7508,11 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             AllOf(source, WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+                  WithToolType(ToolType::STYLUS))));
     ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
 
     // There is another tool type change.
-    mStylusState.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+    mStylusState.toolType = ToolType::FINGER;
     processExternalStylusState(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
     ASSERT_NO_FATAL_FAILURE(
@@ -7394,13 +7523,13 @@
     handleTimeout(mapper, ARBITRARY_TIME + TOUCH_DATA_TIMEOUT);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             AllOf(source, WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))));
+                  WithToolType(ToolType::FINGER))));
 
     processUp(mapper);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             AllOf(source, WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))));
+                  WithToolType(ToolType::FINGER))));
 
     ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
@@ -7409,7 +7538,7 @@
 TEST_F(ExternalStylusFusionTest, FusedPointerReportsButtons) {
     SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
     auto toolTypeSource =
-            AllOf(WithSource(EXPECTED_SOURCE), WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS));
+            AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS));
 
     ASSERT_NO_FATAL_FAILURE(testStartFusedStylusGesture(mapper));
 
@@ -7600,7 +7729,7 @@
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION);
     prepareVirtualKeys();
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
 
@@ -7626,7 +7755,7 @@
     ASSERT_EQ(0, motionArgs.edgeFlags);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
@@ -7645,9 +7774,9 @@
     ASSERT_EQ(0, motionArgs.edgeFlags);
     ASSERT_EQ(size_t(2), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
@@ -7676,9 +7805,9 @@
     ASSERT_EQ(0, motionArgs.edgeFlags);
     ASSERT_EQ(size_t(2), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
@@ -7705,9 +7834,9 @@
     ASSERT_EQ(0, motionArgs.edgeFlags);
     ASSERT_EQ(size_t(2), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
@@ -7728,7 +7857,7 @@
     ASSERT_EQ(0, motionArgs.edgeFlags);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(1, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
@@ -7753,7 +7882,7 @@
     ASSERT_EQ(0, motionArgs.edgeFlags);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(1, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
@@ -7780,9 +7909,9 @@
     ASSERT_EQ(0, motionArgs.edgeFlags);
     ASSERT_EQ(size_t(2), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
@@ -7809,9 +7938,9 @@
     ASSERT_EQ(0, motionArgs.edgeFlags);
     ASSERT_EQ(size_t(2), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
@@ -7832,7 +7961,7 @@
     ASSERT_EQ(0, motionArgs.edgeFlags);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
@@ -7855,7 +7984,7 @@
     ASSERT_EQ(0, motionArgs.edgeFlags);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
@@ -7884,7 +8013,7 @@
     mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MINOR, RAW_TOOL_MIN, RAW_TOOL_MAX,
                                    /*flat*/ 0, /*flat*/ 0, /*resolution*/ 15);
 
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     // X and Y axes
     assertAxisResolution(mapper, AMOTION_EVENT_AXIS_X, 10 / X_PRECISION);
@@ -7908,7 +8037,7 @@
 
     // We do not add ABS_MT_TOUCH_MAJOR / MINOR or ABS_MT_WIDTH_MAJOR / MINOR axes
 
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     // Touch major and minor
     assertAxisNotPresent(mapper, AMOTION_EVENT_AXIS_TOUCH_MAJOR);
@@ -7923,7 +8052,7 @@
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID);
     prepareVirtualKeys();
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
 
@@ -7943,7 +8072,7 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
 
@@ -7951,9 +8080,9 @@
     ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action);
     ASSERT_EQ(size_t(2), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
@@ -7973,9 +8102,9 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
     ASSERT_EQ(size_t(2), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
@@ -7992,9 +8121,9 @@
     ASSERT_EQ(ACTION_POINTER_0_UP, motionArgs.action);
     ASSERT_EQ(size_t(2), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
@@ -8004,7 +8133,7 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(1, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
 
@@ -8019,7 +8148,7 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(1, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
 
@@ -8037,9 +8166,9 @@
     ASSERT_EQ(ACTION_POINTER_0_DOWN, motionArgs.action);
     ASSERT_EQ(size_t(2), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
@@ -8056,9 +8185,9 @@
     ASSERT_EQ(ACTION_POINTER_1_UP, motionArgs.action);
     ASSERT_EQ(size_t(2), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
@@ -8068,7 +8197,7 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
 
@@ -8080,7 +8209,7 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
 
@@ -8094,7 +8223,7 @@
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT);
     prepareVirtualKeys();
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
 
@@ -8113,7 +8242,7 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
 
@@ -8121,9 +8250,9 @@
     ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action);
     ASSERT_EQ(size_t(2), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
@@ -8141,9 +8270,9 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
     ASSERT_EQ(size_t(2), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
@@ -8161,9 +8290,9 @@
     ASSERT_EQ(ACTION_POINTER_0_UP, motionArgs.action);
     ASSERT_EQ(size_t(2), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
@@ -8173,7 +8302,7 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(1, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
 
@@ -8186,7 +8315,7 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(1, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
 
@@ -8202,9 +8331,9 @@
     ASSERT_EQ(ACTION_POINTER_0_DOWN, motionArgs.action);
     ASSERT_EQ(size_t(2), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
@@ -8222,9 +8351,9 @@
     ASSERT_EQ(ACTION_POINTER_1_UP, motionArgs.action);
     ASSERT_EQ(size_t(2), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
@@ -8234,7 +8363,7 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
 
@@ -8246,7 +8375,7 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
     ASSERT_EQ(size_t(1), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
 
@@ -8259,7 +8388,7 @@
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | TOUCH | TOOL | PRESSURE | ORIENTATION | ID | MINOR | DISTANCE);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     // These calculations are based on the input device calibration documentation.
     int32_t rawX = 100;
@@ -8309,7 +8438,7 @@
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | TOUCH | TOOL | MINOR);
     addConfigurationProperty("touch.size.calibration", "geometric");
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     // These calculations are based on the input device calibration documentation.
     int32_t rawX = 100;
@@ -8349,7 +8478,7 @@
     addConfigurationProperty("touch.size.scale", "10");
     addConfigurationProperty("touch.size.bias", "160");
     addConfigurationProperty("touch.size.isSummed", "1");
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     // These calculations are based on the input device calibration documentation.
     // Note: We only provide a single common touch/tool value because the device is assumed
@@ -8399,7 +8528,7 @@
     addConfigurationProperty("touch.size.calibration", "area");
     addConfigurationProperty("touch.size.scale", "43");
     addConfigurationProperty("touch.size.bias", "3");
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     // These calculations are based on the input device calibration documentation.
     int32_t rawX = 100;
@@ -8431,10 +8560,10 @@
     prepareAxes(POSITION | PRESSURE);
     addConfigurationProperty("touch.pressure.calibration", "amplitude");
     addConfigurationProperty("touch.pressure.scale", "0.01");
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     InputDeviceInfo info;
-    mapper.populateDeviceInfo(&info);
+    mapper.populateDeviceInfo(info);
     ASSERT_NO_FATAL_FAILURE(assertMotionRange(info,
             AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_TOUCHSCREEN,
             0.0f, RAW_PRESSURE_MAX * 0.01, 0.0f, 0.0f));
@@ -8463,7 +8592,7 @@
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     NotifyMotionArgs motionArgs;
     NotifyKeyArgs keyArgs;
@@ -8706,7 +8835,7 @@
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_A, 0, AKEYCODE_STYLUS_BUTTON_PRIMARY, 0);
     mFakeEventHub->addKey(EVENTHUB_ID, 0, 0xabcd, AKEYCODE_STYLUS_BUTTON_SECONDARY, 0);
@@ -8763,7 +8892,7 @@
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | TOOL_TYPE);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     NotifyMotionArgs motionArgs;
 
@@ -8773,14 +8902,14 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 
     // eraser
     processKey(mapper, BTN_TOOL_RUBBER, 1);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_ERASER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::ERASER, motionArgs.pointerProperties[0].toolType);
 
     // stylus
     processKey(mapper, BTN_TOOL_RUBBER, 0);
@@ -8788,7 +8917,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType);
 
     // brush
     processKey(mapper, BTN_TOOL_PEN, 0);
@@ -8796,7 +8925,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType);
 
     // pencil
     processKey(mapper, BTN_TOOL_BRUSH, 0);
@@ -8804,7 +8933,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType);
 
     // air-brush
     processKey(mapper, BTN_TOOL_PENCIL, 0);
@@ -8812,7 +8941,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType);
 
     // mouse
     processKey(mapper, BTN_TOOL_AIRBRUSH, 0);
@@ -8820,7 +8949,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::MOUSE, motionArgs.pointerProperties[0].toolType);
 
     // lens
     processKey(mapper, BTN_TOOL_MOUSE, 0);
@@ -8828,7 +8957,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::MOUSE, motionArgs.pointerProperties[0].toolType);
 
     // double-tap
     processKey(mapper, BTN_TOOL_LENS, 0);
@@ -8836,7 +8965,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 
     // triple-tap
     processKey(mapper, BTN_TOOL_DOUBLETAP, 0);
@@ -8844,7 +8973,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 
     // quad-tap
     processKey(mapper, BTN_TOOL_TRIPLETAP, 0);
@@ -8852,7 +8981,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 
     // finger
     processKey(mapper, BTN_TOOL_QUADTAP, 0);
@@ -8860,42 +8989,42 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 
     // stylus trumps finger
     processKey(mapper, BTN_TOOL_PEN, 1);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType);
 
     // eraser trumps stylus
     processKey(mapper, BTN_TOOL_RUBBER, 1);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_ERASER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::ERASER, motionArgs.pointerProperties[0].toolType);
 
     // mouse trumps eraser
     processKey(mapper, BTN_TOOL_MOUSE, 1);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::MOUSE, motionArgs.pointerProperties[0].toolType);
 
     // MT tool type trumps BTN tool types: MT_TOOL_FINGER
     processToolType(mapper, MT_TOOL_FINGER); // this is the first time we send MT_TOOL_TYPE
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 
     // MT tool type trumps BTN tool types: MT_TOOL_PEN
     processToolType(mapper, MT_TOOL_PEN);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType);
 
     // back to default tool type
     processToolType(mapper, -1); // use a deliberately undefined tool type, for testing
@@ -8906,7 +9035,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 }
 
 TEST_F(MultiTouchInputMapperTest, Process_WhenBtnTouchPresent_HoversIfItsValueIsZero) {
@@ -8914,7 +9043,7 @@
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     NotifyMotionArgs motionArgs;
 
@@ -8984,7 +9113,7 @@
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | PRESSURE);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     NotifyMotionArgs motionArgs;
 
@@ -9065,7 +9194,7 @@
 
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareAxes(POSITION);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     mFakePolicy->addInputPortAssociation(DEVICE_LOCATION, hdmi1);
     mFakePolicy->addInputPortAssociation(usb2, hdmi2);
@@ -9095,7 +9224,7 @@
 TEST_F(MultiTouchInputMapperTest, Configure_AssignsDisplayUniqueId) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareAxes(POSITION);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, VIRTUAL_DISPLAY_UNIQUE_ID);
 
@@ -9117,7 +9246,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);
@@ -9125,7 +9253,7 @@
 
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     // Check source is mouse that would obtain the PointerController.
     ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
@@ -9145,7 +9273,7 @@
 TEST_F(MultiTouchInputMapperTest, Process_SendsReadTime) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareAxes(POSITION);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     prepareDisplay(ui::ROTATION_0);
     process(mapper, 10, /*readTime=*/11, EV_ABS, ABS_MT_TRACKING_ID, 1);
@@ -9174,9 +9302,9 @@
     // Don't set touch.enableForInactiveViewport to verify the default behavior.
     mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
                                     /*isActive=*/false, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
-    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
     prepareAxes(POSITION);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     NotifyMotionArgs motionArgs;
     processPosition(mapper, 100, 100);
@@ -9194,9 +9322,9 @@
     addConfigurationProperty("touch.enableForInactiveViewport", "1");
     mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
                                     /*isActive=*/false, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
-    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
     prepareAxes(POSITION);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     NotifyMotionArgs motionArgs;
     processPosition(mapper, 100, 100);
@@ -9216,9 +9344,9 @@
     ASSERT_TRUE(optionalDisplayViewport.has_value());
     DisplayViewport displayViewport = *optionalDisplayViewport;
 
-    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
     prepareAxes(POSITION);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     // Finger down
     int32_t x = 100, y = 100;
@@ -9232,7 +9360,7 @@
     // Deactivate display viewport
     displayViewport.isActive = false;
     ASSERT_TRUE(mFakePolicy->updateViewport(displayViewport));
-    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
 
     // The ongoing touch should be canceled immediately
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
@@ -9247,7 +9375,7 @@
     // Reactivate display viewport
     displayViewport.isActive = true;
     ASSERT_TRUE(mFakePolicy->updateViewport(displayViewport));
-    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
 
     // Finger move again starts new gesture
     x += 10, y += 10;
@@ -9261,7 +9389,7 @@
     // Setup the first touch screen device.
     prepareAxes(POSITION | ID | SLOT);
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     // Create the second touch screen device, and enable multi fingers.
     const std::string USB2 = "USB2";
@@ -9285,10 +9413,12 @@
                                             String8("touchScreen"));
 
     // Setup the second touch screen device.
-    MultiTouchInputMapper& mapper2 = device2->addMapper<MultiTouchInputMapper>(SECOND_EVENTHUB_ID);
+    device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID);
+    MultiTouchInputMapper& mapper2 = device2->constructAndAddMapper<
+            MultiTouchInputMapper>(SECOND_EVENTHUB_ID, mFakePolicy->getReaderConfiguration());
     std::list<NotifyArgs> unused =
             device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                               /*changes=*/0);
+                               /*changes=*/{});
     unused += device2->reset(ARBITRARY_TIME);
 
     // Setup PointerController.
@@ -9309,8 +9439,8 @@
 
     // Default device will reconfigure above, need additional reconfiguration for another device.
     unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                                 InputReaderConfiguration::CHANGE_DISPLAY_INFO |
-                                         InputReaderConfiguration::CHANGE_SHOW_TOUCHES);
+                                 InputReaderConfiguration::Change::DISPLAY_INFO |
+                                         InputReaderConfiguration::Change::SHOW_TOUCHES);
 
     // Two fingers down at default display.
     int32_t x1 = 100, y1 = 125, x2 = 300, y2 = 500;
@@ -9341,7 +9471,7 @@
     // Disable the show touches configuration and ensure the spots are cleared.
     mFakePolicy->setShowTouches(false);
     unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                                 InputReaderConfiguration::CHANGE_SHOW_TOUCHES);
+                                 InputReaderConfiguration::Change::SHOW_TOUCHES);
 
     ASSERT_TRUE(fakePointerController->getSpots().empty());
 }
@@ -9350,7 +9480,7 @@
     prepareAxes(POSITION);
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     NotifyMotionArgs motionArgs;
     // Unrotated video frame
@@ -9374,7 +9504,7 @@
 TEST_F(MultiTouchInputMapperTest, VideoFrames_AreNotRotated) {
     prepareAxes(POSITION);
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
     // Unrotated video frame
     TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, {1, 2});
     NotifyMotionArgs motionArgs;
@@ -9399,7 +9529,7 @@
     // Since InputReader works in the un-rotated coordinate space, only devices that are not
     // orientation-aware are affected by display rotation.
     addConfigurationProperty("touch.orientationAware", "0");
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
     // Unrotated video frame
     TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, {1, 2});
     NotifyMotionArgs motionArgs;
@@ -9430,7 +9560,7 @@
 TEST_F(MultiTouchInputMapperTest, VideoFrames_MultipleFramesAreNotRotated) {
     prepareAxes(POSITION);
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
     // Unrotated video frames. There's no rule that they must all have the same dimensions,
     // so mix these.
     TouchVideoFrame frame1(3, 2, {1, 2, 3, 4, 5, 6}, {1, 2});
@@ -9453,7 +9583,7 @@
     // Since InputReader works in the un-rotated coordinate space, only devices that are not
     // orientation-aware are affected by display rotation.
     addConfigurationProperty("touch.orientationAware", "0");
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
     // Unrotated video frames. There's no rule that they must all have the same dimensions,
     // so mix these.
     TouchVideoFrame frame1(3, 2, {1, 2, 3, 4, 5, 6}, {1, 2});
@@ -9490,7 +9620,7 @@
 
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareAxes(POSITION);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     ASSERT_EQ(mDevice->isEnabled(), false);
 
@@ -9511,7 +9641,7 @@
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | TOOL_TYPE);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     NotifyMotionArgs motionArgs;
 
@@ -9522,7 +9652,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 
     // finger move
     processId(mapper, 1);
@@ -9530,14 +9660,14 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 
     // finger up.
     processId(mapper, -1);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 
     // new finger down
     processId(mapper, 1);
@@ -9545,7 +9675,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 }
 
 /**
@@ -9556,7 +9686,7 @@
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | TOOL_TYPE);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     NotifyMotionArgs motionArgs;
 
@@ -9567,7 +9697,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 
     // Tool changed to MT_TOOL_PALM expect sending the cancel event.
     processToolType(mapper, MT_TOOL_PALM);
@@ -9593,7 +9723,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 }
 
 /**
@@ -9604,7 +9734,7 @@
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | TOOL_TYPE);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     NotifyMotionArgs motionArgs;
 
@@ -9615,7 +9745,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 
     // Second finger down.
     processSlot(mapper, SECOND_SLOT);
@@ -9624,7 +9754,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType);
 
     // If the tool type of the first finger changes to MT_TOOL_PALM,
     // we expect to receive ACTION_POINTER_UP with cancel flag.
@@ -9679,7 +9809,7 @@
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | TOOL_TYPE);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     NotifyMotionArgs motionArgs;
 
@@ -9690,7 +9820,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 
     // Second finger down.
     processSlot(mapper, SECOND_SLOT);
@@ -9699,7 +9829,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 
     // If the tool type of the first finger changes to MT_TOOL_PALM,
     // we expect to receive ACTION_POINTER_UP with cancel flag.
@@ -9734,7 +9864,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(uint32_t(1), motionArgs.pointerCount);
 
     // third finger move
@@ -9777,7 +9907,7 @@
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | TOOL_TYPE);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     NotifyMotionArgs motionArgs;
 
@@ -9788,7 +9918,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 
     // Second finger down.
     processSlot(mapper, SECOND_SLOT);
@@ -9797,7 +9927,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 
     // If the tool type of the second finger changes to MT_TOOL_PALM,
     // we expect to receive ACTION_POINTER_UP with cancel flag.
@@ -9849,7 +9979,7 @@
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | PRESSURE);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     NotifyMotionArgs motionArgs;
 
@@ -9906,7 +10036,7 @@
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | PRESSURE);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     // First finger down.
     processId(mapper, FIRST_TRACKING_ID);
@@ -9947,7 +10077,7 @@
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | PRESSURE);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     // First finger touches down and releases.
     processId(mapper, FIRST_TRACKING_ID);
@@ -9975,7 +10105,7 @@
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | PRESSURE | TOOL_TYPE);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
 
     // Even if the device supports reporting the ABS_MT_TOOL_TYPE axis, which could give it the
@@ -9994,7 +10124,7 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
                   WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+                  WithToolType(ToolType::STYLUS))));
 
     // Now that we know the device supports styluses, ensure that the device is re-configured with
     // the stylus source.
@@ -10016,7 +10146,7 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
                   WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+                  WithToolType(ToolType::STYLUS))));
 }
 
 TEST_F(MultiTouchInputMapperTest, Process_WhenConfigEnabled_ShouldShowDirectStylusPointer) {
@@ -10030,7 +10160,7 @@
             std::make_shared<FakePointerController>();
     mFakePolicy->setPointerController(fakePointerController);
     mFakePolicy->setStylusPointerIconEnabled(true);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     processId(mapper, FIRST_TRACKING_ID);
     processPressure(mapper, RAW_PRESSURE_MIN);
@@ -10039,7 +10169,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS),
+                  WithToolType(ToolType::STYLUS),
                   WithPointerCoords(0, toDisplayX(100), toDisplayY(200)))));
     ASSERT_TRUE(fakePointerController->isPointerShown());
     ASSERT_NO_FATAL_FAILURE(
@@ -10057,7 +10187,7 @@
             std::make_shared<FakePointerController>();
     mFakePolicy->setPointerController(fakePointerController);
     mFakePolicy->setStylusPointerIconEnabled(false);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     processId(mapper, FIRST_TRACKING_ID);
     processPressure(mapper, RAW_PRESSURE_MIN);
@@ -10066,7 +10196,7 @@
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS),
+                  WithToolType(ToolType::STYLUS),
                   WithPointerCoords(0, toDisplayX(100), toDisplayY(200)))));
     ASSERT_FALSE(fakePointerController->isPointerShown());
 }
@@ -10085,7 +10215,7 @@
     prepareAxes(POSITION);
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources());
 
@@ -10112,7 +10242,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);
@@ -10121,7 +10250,7 @@
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0);
     mFakePolicy->setPointerCapture(true);
     mFakePolicy->setPointerController(fakePointerController);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     // captured touchpad should be a touchpad source
     NotifyDeviceResetArgs resetArgs;
@@ -10252,7 +10381,7 @@
 
     // non captured touchpad should be a mouse source
     mFakePolicy->setPointerCapture(false);
-    configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
+    configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
     ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
 }
@@ -10262,7 +10391,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);
@@ -10270,7 +10398,7 @@
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0);
     mFakePolicy->setPointerController(fakePointerController);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
     // run uncaptured pointer tests - pushes out generic events
     // FINGER 0 DOWN
     processId(mapper, 3);
@@ -10331,14 +10459,14 @@
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0);
     mFakePolicy->setPointerController(fakePointerController);
     mFakePolicy->setPointerCapture(false);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     // uncaptured touchpad should be a pointer device
     ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
 
     // captured touchpad should be a touchpad device
     mFakePolicy->setPointerCapture(true);
-    configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
+    configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
     ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
 }
 
@@ -10355,7 +10483,7 @@
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | PRESSURE);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     nsecs_t kernelEventTime = ARBITRARY_TIME;
     nsecs_t expectedEventTime = ARBITRARY_TIME;
@@ -10402,7 +10530,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);
@@ -10443,7 +10570,7 @@
     // which is greater than fraction of the diagnal length of the touchpad (349).
     // Thus, MaxSwipWidth is 750.
     preparePointerMode(/*xResolution=*/25, /*yResolution=*/25);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
     NotifyMotionArgs motionArgs;
 
     // Two fingers down at once.
@@ -10462,7 +10589,7 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(1U, motionArgs.pointerCount);
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(MotionClassification::NONE, motionArgs.classification);
     ASSERT_NO_FATAL_FAILURE(
             assertPointerCoords(motionArgs.pointerCoords[0], 0, 0, 1, 0, 0, 0, 0, 0, 0, 0));
@@ -10484,7 +10611,7 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(1U, motionArgs.pointerCount);
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(MotionClassification::TWO_FINGER_SWIPE, motionArgs.classification);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], 0,
                                                 movingDistance * mPointerMovementScale, 1, 0, 0, 0,
@@ -10503,7 +10630,7 @@
     // which is greater than fraction of the diagnal length of the touchpad (349).
     // Thus, MaxSwipWidth is the fraction of the diagnal length, 349.
     preparePointerMode(/*xResolution=*/5, /*yResolution=*/5);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
     NotifyMotionArgs motionArgs;
 
     // Two fingers down at once.
@@ -10522,7 +10649,7 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(1U, motionArgs.pointerCount);
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(MotionClassification::NONE, motionArgs.classification);
     ASSERT_NO_FATAL_FAILURE(
             assertPointerCoords(motionArgs.pointerCoords[0], 0, 0, 1, 0, 0, 0, 0, 0, 0, 0));
@@ -10544,7 +10671,7 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(1U, motionArgs.pointerCount);
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(MotionClassification::TWO_FINGER_SWIPE, motionArgs.classification);
     // New coordinate is the scaled relative coordinate from the initial coordinate.
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], 0,
@@ -10559,7 +10686,7 @@
  */
 TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthFreeform) {
     preparePointerMode(/*xResolution=*/25, /*yResolution=*/25);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     NotifyMotionArgs motionArgs;
 
@@ -10578,7 +10705,7 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(1U, motionArgs.pointerCount);
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(MotionClassification::NONE, motionArgs.classification);
     // One pointer for PRESS, and its coordinate is used as the origin for pointer coordinates.
     ASSERT_NO_FATAL_FAILURE(
@@ -10604,15 +10731,15 @@
     ASSERT_EQ(1U, motionArgs.pointerCount);
     ASSERT_EQ(AMOTION_EVENT_ACTION_CANCEL, motionArgs.action);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(1U, motionArgs.pointerCount);
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(MotionClassification::NONE, motionArgs.classification);
     ASSERT_EQ(2U, motionArgs.pointerCount);
     ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN, motionArgs.action & AMOTION_EVENT_ACTION_MASK);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(MotionClassification::NONE, motionArgs.classification);
     // Two pointers' scaled relative coordinates from their initial centroid.
     // Initial y coordinates are 0 as y1 and y2 have the same value.
@@ -10642,7 +10769,7 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(2U, motionArgs.pointerCount);
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
     ASSERT_EQ(MotionClassification::NONE, motionArgs.classification);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], cookedX1,
                                                 movingDistance * 2 * mPointerMovementScale, 1, 0, 0,
@@ -10654,7 +10781,7 @@
 
 TEST_F(MultiTouchPointerModeTest, TwoFingerSwipeOffsets) {
     preparePointerMode(/*xResolution=*/25, /*yResolution=*/25);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
     NotifyMotionArgs motionArgs;
 
     // Place two fingers down.
@@ -10701,7 +10828,7 @@
 TEST_F(MultiTouchPointerModeTest, WhenViewportActiveStatusChanged_PointerGestureIsReset) {
     preparePointerMode(/*xResolution=*/25, /*yResolution=*/25);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
 
     // Start a stylus gesture.
@@ -10712,12 +10839,12 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
                   WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+                  WithToolType(ToolType::STYLUS))));
     // TODO(b/257078296): Pointer mode generates extra event.
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
                   WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+                  WithToolType(ToolType::STYLUS))));
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
 
     // Make the viewport inactive. This will put the device in disabled mode, and the ongoing stylus
@@ -10725,16 +10852,16 @@
     auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
     viewport->isActive = false;
     mFakePolicy->updateViewport(*viewport);
-    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL),
                   WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+                  WithToolType(ToolType::STYLUS))));
     // TODO(b/257078296): Pointer mode generates extra event.
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL),
                   WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS),
-                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+                  WithToolType(ToolType::STYLUS))));
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
 }
 
@@ -10777,7 +10904,7 @@
 
 TEST_F(JoystickInputMapperTest, Configure_AssignsDisplayUniqueId) {
     prepareAxes();
-    JoystickInputMapper& mapper = addMapperAndConfigure<JoystickInputMapper>();
+    JoystickInputMapper& mapper = constructAndAddMapper<JoystickInputMapper>();
 
     mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, VIRTUAL_DISPLAY_UNIQUE_ID);
 
@@ -10833,14 +10960,6 @@
         mFakePolicy.clear();
     }
 
-    std::list<NotifyArgs> configureDevice(uint32_t changes) {
-        if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
-            mReader->requestRefreshConfiguration(changes);
-            mReader->loopOnce();
-        }
-        return mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), changes);
-    }
-
     std::shared_ptr<InputDevice> newDevice(int32_t deviceId, const std::string& name,
                                            const std::string& location, int32_t eventHubId,
                                            ftl::Flags<InputDeviceClass> classes) {
diff --git a/services/inputflinger/tests/NotifyArgs_test.cpp b/services/inputflinger/tests/NotifyArgs_test.cpp
index 6715585..1536756 100644
--- a/services/inputflinger/tests/NotifyArgs_test.cpp
+++ b/services/inputflinger/tests/NotifyArgs_test.cpp
@@ -54,7 +54,7 @@
     for (size_t i = 0; i < pointerCount; i++) {
         pointerProperties[i].clear();
         pointerProperties[i].id = i;
-        pointerProperties[i].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+        pointerProperties[i].toolType = ToolType::FINGER;
 
         pointerCoords[i].clear();
         pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, x++);
diff --git a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
index 9014dfb..9818176 100644
--- a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
+++ b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
@@ -45,8 +45,8 @@
     PointerCoords pointerCoords[pointerCount];
 
     const int32_t deviceId = isFromSource(source, TOUCHSCREEN) ? TOUCH_DEVICE_ID : STYLUS_DEVICE_ID;
-    const int32_t toolType = isFromSource(source, TOUCHSCREEN) ? AMOTION_EVENT_TOOL_TYPE_FINGER
-                                                               : AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    const ToolType toolType =
+            isFromSource(source, TOUCHSCREEN) ? ToolType::FINGER : ToolType::STYLUS;
     for (size_t i = 0; i < pointerCount; i++) {
         pointerProperties[i].clear();
         pointerProperties[i].id = i;
@@ -278,20 +278,20 @@
     // Event from a stylus device, but with finger tool type
     args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/1, DOWN, {{1, 2}}, STYLUS);
     // Keep source stylus, but make the tool type touch
-    args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+    args.pointerProperties[0].toolType = ToolType::FINGER;
     assertNotBlocked(args);
 
     // Second pointer (stylus pointer) goes down, from the same device
     args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/2, POINTER_1_DOWN, {{1, 2}, {10, 20}},
                               STYLUS);
     // Keep source stylus, but make the tool type touch
-    args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    args.pointerProperties[0].toolType = ToolType::STYLUS;
     assertNotBlocked(args);
 
     // Second pointer (stylus pointer) goes down, from the same device
     args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/3, MOVE, {{2, 3}, {11, 21}}, STYLUS);
     // Keep source stylus, but make the tool type touch
-    args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+    args.pointerProperties[0].toolType = ToolType::FINGER;
     assertNotBlocked(args);
 }
 
@@ -418,14 +418,14 @@
     // Introduce a stylus pointer into the device 1 stream. It should be ignored.
     args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/3, POINTER_1_DOWN, {{1, 2}, {3, 4}},
                               TOUCHSCREEN);
-    args.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    args.pointerProperties[1].toolType = ToolType::STYLUS;
     args.source = STYLUS;
     assertDropped(args);
 
     // Lift up touch from the mixed touch/stylus device
     args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/4, CANCEL, {{1, 2}, {3, 4}},
                               TOUCHSCREEN);
-    args.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    args.pointerProperties[1].toolType = ToolType::STYLUS;
     args.source = STYLUS;
     assertDropped(args);
 
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/TestInputListener.cpp b/services/inputflinger/tests/TestInputListener.cpp
index 2801072..fc917dd 100644
--- a/services/inputflinger/tests/TestInputListener.cpp
+++ b/services/inputflinger/tests/TestInputListener.cpp
@@ -29,6 +29,14 @@
 
 TestInputListener::~TestInputListener() {}
 
+void TestInputListener::assertNotifyInputDevicesChangedWasCalled(
+        NotifyInputDevicesChangedArgs* outEventArgs) {
+    ASSERT_NO_FATAL_FAILURE(
+            assertCalled<NotifyInputDevicesChangedArgs>(outEventArgs,
+                                                        "Expected notifyInputDevicesChanged() "
+                                                        "to have been called."));
+}
+
 void TestInputListener::assertNotifyConfigurationChangedWasCalled(
         NotifyConfigurationChangedArgs* outEventArgs) {
     ASSERT_NO_FATAL_FAILURE(
@@ -160,43 +168,47 @@
 }
 
 template <class NotifyArgsType>
-void TestInputListener::addToQueue(const NotifyArgsType* args) {
+void TestInputListener::addToQueue(const NotifyArgsType& args) {
     std::scoped_lock<std::mutex> lock(mLock);
 
     std::vector<NotifyArgsType>& queue = std::get<std::vector<NotifyArgsType>>(mQueues);
-    queue.push_back(*args);
+    queue.push_back(args);
     mCondition.notify_all();
 }
 
-void TestInputListener::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) {
+void TestInputListener::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
+    addToQueue<NotifyInputDevicesChangedArgs>(args);
+}
+
+void TestInputListener::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
     addToQueue<NotifyConfigurationChangedArgs>(args);
 }
 
-void TestInputListener::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
+void TestInputListener::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
     addToQueue<NotifyDeviceResetArgs>(args);
 }
 
-void TestInputListener::notifyKey(const NotifyKeyArgs* args) {
+void TestInputListener::notifyKey(const NotifyKeyArgs& args) {
     addToQueue<NotifyKeyArgs>(args);
 }
 
-void TestInputListener::notifyMotion(const NotifyMotionArgs* args) {
+void TestInputListener::notifyMotion(const NotifyMotionArgs& args) {
     addToQueue<NotifyMotionArgs>(args);
 }
 
-void TestInputListener::notifySwitch(const NotifySwitchArgs* args) {
+void TestInputListener::notifySwitch(const NotifySwitchArgs& args) {
     addToQueue<NotifySwitchArgs>(args);
 }
 
-void TestInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) {
+void TestInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) {
     addToQueue<NotifyPointerCaptureChangedArgs>(args);
 }
 
-void TestInputListener::notifySensor(const NotifySensorArgs* args) {
+void TestInputListener::notifySensor(const NotifySensorArgs& args) {
     addToQueue<NotifySensorArgs>(args);
 }
 
-void TestInputListener::notifyVibratorState(const NotifyVibratorStateArgs* args) {
+void TestInputListener::notifyVibratorState(const NotifyVibratorStateArgs& args) {
     addToQueue<NotifyVibratorStateArgs>(args);
 }
 
diff --git a/services/inputflinger/tests/TestInputListener.h b/services/inputflinger/tests/TestInputListener.h
index 9665f70..deb6048 100644
--- a/services/inputflinger/tests/TestInputListener.h
+++ b/services/inputflinger/tests/TestInputListener.h
@@ -35,6 +35,9 @@
 
     using TimePoint = std::chrono::time_point<std::chrono::system_clock>;
 
+    void assertNotifyInputDevicesChangedWasCalled(
+            NotifyInputDevicesChangedArgs* outEventArgs = nullptr);
+
     void assertNotifyConfigurationChangedWasCalled(
             NotifyConfigurationChangedArgs* outEventArgs = nullptr);
 
@@ -74,30 +77,33 @@
     void assertNotCalled(std::string message, std::optional<TimePoint> timeout = {});
 
     template <class NotifyArgsType>
-    void addToQueue(const NotifyArgsType* args);
+    void addToQueue(const NotifyArgsType& args);
 
-    virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override;
+    virtual void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
 
-    virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) override;
+    virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
 
-    virtual void notifyKey(const NotifyKeyArgs* args) override;
+    virtual void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
 
-    virtual void notifyMotion(const NotifyMotionArgs* args) override;
+    virtual void notifyKey(const NotifyKeyArgs& args) override;
 
-    virtual void notifySwitch(const NotifySwitchArgs* args) override;
+    virtual void notifyMotion(const NotifyMotionArgs& args) override;
 
-    virtual void notifySensor(const NotifySensorArgs* args) override;
+    virtual void notifySwitch(const NotifySwitchArgs& args) override;
 
-    virtual void notifyVibratorState(const NotifyVibratorStateArgs* args) override;
+    virtual void notifySensor(const NotifySensorArgs& args) override;
 
-    virtual void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override;
+    virtual void notifyVibratorState(const NotifyVibratorStateArgs& args) override;
+
+    virtual void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
 
     std::mutex mLock;
     std::condition_variable mCondition;
     const std::chrono::milliseconds mEventHappenedTimeout;
     const std::chrono::milliseconds mEventDidNotHappenTimeout;
 
-    std::tuple<std::vector<NotifyConfigurationChangedArgs>,  //
+    std::tuple<std::vector<NotifyInputDevicesChangedArgs>,   //
+               std::vector<NotifyConfigurationChangedArgs>,  //
                std::vector<NotifyDeviceResetArgs>,           //
                std::vector<NotifyKeyArgs>,                   //
                std::vector<NotifyMotionArgs>,                //
diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h
index edd14f8..db6f254 100644
--- a/services/inputflinger/tests/TestInputListenerMatchers.h
+++ b/services/inputflinger/tests/TestInputListenerMatchers.h
@@ -73,6 +73,12 @@
     return arg.pointerCount == count;
 }
 
+MATCHER_P2(WithPointerId, index, id, "MotionEvent with specified pointer ID for pointer index") {
+    const auto argPointerId = arg.pointerProperties[index].id;
+    *result_listener << "expected pointer with index " << index << " to have ID " << argPointerId;
+    return argPointerId == id;
+}
+
 MATCHER_P2(WithCoords, x, y, "InputEvent with specified coords") {
     const auto argX = arg.pointerCoords[0].getX();
     const auto argY = arg.pointerCoords[0].getY();
@@ -136,16 +142,40 @@
     return argPressure == pressure;
 }
 
+MATCHER_P2(WithTouchDimensions, maj, min, "InputEvent with specified touch dimensions") {
+    const auto argMajor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
+    const auto argMinor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);
+    *result_listener << "expected touch dimensions " << maj << " major x " << min
+                     << " minor, but got " << argMajor << " major x " << argMinor << " minor";
+    return argMajor == maj && argMinor == min;
+}
+
+MATCHER_P2(WithToolDimensions, maj, min, "InputEvent with specified tool dimensions") {
+    const auto argMajor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR);
+    const auto argMinor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR);
+    *result_listener << "expected tool dimensions " << maj << " major x " << min
+                     << " minor, but got " << argMajor << " major x " << argMinor << " minor";
+    return argMajor == maj && argMinor == min;
+}
+
 MATCHER_P(WithToolType, toolType, "InputEvent with specified tool type") {
     const auto argToolType = arg.pointerProperties[0].toolType;
-    *result_listener << "expected tool type " << motionToolTypeToString(toolType) << ", but got "
-                     << motionToolTypeToString(argToolType);
+    *result_listener << "expected tool type " << ftl::enum_string(toolType) << ", but got "
+                     << ftl::enum_string(argToolType);
+    return argToolType == toolType;
+}
+
+MATCHER_P2(WithPointerToolType, pointer, toolType,
+           "InputEvent with specified tool type for pointer") {
+    const auto argToolType = arg.pointerProperties[pointer].toolType;
+    *result_listener << "expected pointer " << pointer << " to have tool type "
+                     << ftl::enum_string(toolType) << ", but got " << ftl::enum_string(argToolType);
     return argToolType == toolType;
 }
 
 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,
diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
index 3f749b1..1fff2c7 100644
--- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
+++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
@@ -79,7 +79,7 @@
     for (size_t i = 0; i < pointerCount; i++) {
         pointerProperties[i].clear();
         pointerProperties[i].id = i;
-        pointerProperties[i].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+        pointerProperties[i].toolType = ToolType::FINGER;
 
         pointerCoords[i].clear();
         pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, points[i].x);
@@ -421,7 +421,7 @@
     // Create a basic configuration change and send to blocker
     NotifyConfigurationChangedArgs args(/*sequenceNum=*/1, /*eventTime=*/2);
 
-    mBlocker->notifyConfigurationChanged(&args);
+    mBlocker->notifyConfigurationChanged(args);
     NotifyConfigurationChangedArgs outArgs;
     ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyConfigurationChangedWasCalled(&outArgs));
     ASSERT_EQ(args, outArgs);
@@ -438,10 +438,8 @@
                        AKEY_EVENT_ACTION_DOWN, /*flags=*/4, AKEYCODE_HOME, /*scanCode=*/5,
                        AMETA_NONE, /*downTime=*/6);
 
-    mBlocker->notifyKey(&args);
-    NotifyKeyArgs outArgs;
-    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyKeyWasCalled(&outArgs));
-    ASSERT_EQ(args, outArgs);
+    mBlocker->notifyKey(args);
+    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyKeyWasCalled(testing::Eq(args)));
 }
 
 /**
@@ -452,10 +450,8 @@
 TEST_F(UnwantedInteractionBlockerTest, DownEventIsPassedToNextListener) {
     NotifyMotionArgs motionArgs =
             generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}});
-    mBlocker->notifyMotion(&motionArgs);
-    NotifyMotionArgs args;
-    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(motionArgs, args);
+    mBlocker->notifyMotion(motionArgs);
+    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyMotionWasCalled(testing::Eq(motionArgs)));
 }
 
 /**
@@ -466,7 +462,7 @@
     NotifySwitchArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*policyFlags=*/3,
                           /*switchValues=*/4, /*switchMask=*/5);
 
-    mBlocker->notifySwitch(&args);
+    mBlocker->notifySwitch(args);
     NotifySwitchArgs outArgs;
     ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifySwitchWasCalled(&outArgs));
     ASSERT_EQ(args, outArgs);
@@ -479,7 +475,7 @@
 TEST_F(UnwantedInteractionBlockerTest, DeviceResetIsPassedToNextListener) {
     NotifyDeviceResetArgs args(/*sequenceNum=*/1, /*eventTime=*/2, DEVICE_ID);
 
-    mBlocker->notifyDeviceReset(&args);
+    mBlocker->notifyDeviceReset(args);
     NotifyDeviceResetArgs outArgs;
     ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyDeviceResetWasCalled(&outArgs));
     ASSERT_EQ(args, outArgs);
@@ -491,25 +487,20 @@
  * a crash due to inconsistent event stream could have occurred.
  */
 TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenResetHappens) {
-    NotifyMotionArgs args;
-    mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
-    mBlocker->notifyMotion(
-            &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}})));
-    mBlocker->notifyMotion(
-            &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}})));
-    NotifyDeviceResetArgs resetArgs(/*sequenceNum=*/1, /*eventTime=*/3, DEVICE_ID);
-    mBlocker->notifyDeviceReset(&resetArgs);
+    mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
+    mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}}));
+    mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}}));
+    mBlocker->notifyDeviceReset({/*sequenceNum=*/1, /*eventTime=*/3, DEVICE_ID});
     // Start a new gesture with a DOWN event, even though the previous event stream was incomplete.
-    mBlocker->notifyMotion(
-            &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/4, DOWN, {{7, 8, 9}})));
+    mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/4, DOWN, {{7, 8, 9}}));
 }
 
 TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenStylusSourceWithFingerToolIsReceived) {
-    mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
+    mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
     NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}});
-    args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+    args.pointerProperties[0].toolType = ToolType::FINGER;
     args.source = AINPUT_SOURCE_STYLUS;
-    mBlocker->notifyMotion(&args);
+    mBlocker->notifyMotion(args);
 }
 
 /**
@@ -517,48 +508,41 @@
  * UnwantedInteractionBlocker has not changed, there should not be a reset.
  */
 TEST_F(UnwantedInteractionBlockerTest, NoResetIfDeviceInfoChanges) {
-    NotifyMotionArgs args;
-    mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
-    mBlocker->notifyMotion(
-            &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}})));
-    mBlocker->notifyMotion(
-            &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}})));
+    mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
+    mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}}));
+    mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}}));
 
     // Now pretend the device changed, even though nothing is different for DEVICE_ID in practice.
-    mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
+    mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
 
     // The MOVE event continues the gesture that started before 'devices changed', so it should not
     // cause a crash.
-    mBlocker->notifyMotion(
-            &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/4, MOVE, {{7, 8, 9}})));
+    mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/4, MOVE, {{7, 8, 9}}));
 }
 
 /**
  * Send a touch event, and then a stylus event. Make sure that both work.
  */
 TEST_F(UnwantedInteractionBlockerTest, StylusAfterTouchWorks) {
-    NotifyMotionArgs args;
-    mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
-    args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}});
-    mBlocker->notifyMotion(&args);
-    args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{4, 5, 6}});
-    mBlocker->notifyMotion(&args);
-    args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{4, 5, 6}});
-    mBlocker->notifyMotion(&args);
+    mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
+    mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}));
+    mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{4, 5, 6}}));
+    mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{4, 5, 6}}));
 
     // Now touch down stylus
+    NotifyMotionArgs args;
     args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/3, DOWN, {{10, 20, 30}});
-    args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    args.pointerProperties[0].toolType = ToolType::STYLUS;
     args.source |= AINPUT_SOURCE_STYLUS;
-    mBlocker->notifyMotion(&args);
+    mBlocker->notifyMotion(args);
     args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/4, MOVE, {{40, 50, 60}});
-    args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    args.pointerProperties[0].toolType = ToolType::STYLUS;
     args.source |= AINPUT_SOURCE_STYLUS;
-    mBlocker->notifyMotion(&args);
+    mBlocker->notifyMotion(args);
     args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/5, UP, {{40, 50, 60}});
-    args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    args.pointerProperties[0].toolType = ToolType::STYLUS;
     args.source |= AINPUT_SOURCE_STYLUS;
-    mBlocker->notifyMotion(&args);
+    mBlocker->notifyMotion(args);
 }
 
 /**
@@ -568,17 +552,14 @@
  * options
  */
 TEST_F(UnwantedInteractionBlockerTest, DumpCanBeAccessedOnAnotherThread) {
-    mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
-    NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}});
-    mBlocker->notifyMotion(&args1);
+    mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
+    mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}));
     std::thread dumpThread([this]() {
         std::string dump;
         mBlocker->dump(dump);
     });
-    NotifyMotionArgs args2 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{4, 5, 6}});
-    mBlocker->notifyMotion(&args2);
-    NotifyMotionArgs args3 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{4, 5, 6}});
-    mBlocker->notifyMotion(&args3);
+    mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{4, 5, 6}}));
+    mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{4, 5, 6}}));
     dumpThread.join();
 }
 
@@ -587,22 +568,19 @@
  * of the touch is large. This is an integration test that checks that this filter kicks in.
  */
 TEST_F(UnwantedInteractionBlockerTest, HeuristicFilterWorks) {
-    mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
+    mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
     // Small touch down
-    NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}});
-    mBlocker->notifyMotion(&args1);
+    mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}));
     mTestListener.assertNotifyMotionWasCalled(WithMotionAction(DOWN));
 
     // Large touch oval on the next move
-    NotifyMotionArgs args2 =
-            generateMotionArgs(/*downTime=*/0, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}});
-    mBlocker->notifyMotion(&args2);
+    mBlocker->notifyMotion(
+            generateMotionArgs(/*downTime=*/0, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}}));
     mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE));
 
     // Lift up the touch to force the model to decide on whether it's a palm
-    NotifyMotionArgs args3 =
-            generateMotionArgs(/*downTime=*/0, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}});
-    mBlocker->notifyMotion(&args3);
+    mBlocker->notifyMotion(
+            generateMotionArgs(/*downTime=*/0, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}}));
     mTestListener.assertNotifyMotionWasCalled(WithMotionAction(CANCEL));
 }
 
@@ -613,27 +591,27 @@
  * This is similar to `HeuristicFilterWorks` test, but for stylus tool.
  */
 TEST_F(UnwantedInteractionBlockerTest, StylusIsNotBlocked) {
-    InputDeviceInfo info = generateTestDeviceInfo();
-    info.addSource(AINPUT_SOURCE_STYLUS);
-    mBlocker->notifyInputDevicesChanged({info});
+    NotifyInputDevicesChangedArgs deviceChangedArgs = {/*id=*/0, {generateTestDeviceInfo()}};
+    deviceChangedArgs.inputDeviceInfos[0].addSource(AINPUT_SOURCE_STYLUS);
+    mBlocker->notifyInputDevicesChanged(deviceChangedArgs);
     NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}});
-    args1.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
-    mBlocker->notifyMotion(&args1);
+    args1.pointerProperties[0].toolType = ToolType::STYLUS;
+    mBlocker->notifyMotion(args1);
     mTestListener.assertNotifyMotionWasCalled(WithMotionAction(DOWN));
 
     // Move the stylus, setting large TOUCH_MAJOR/TOUCH_MINOR dimensions
     NotifyMotionArgs args2 =
             generateMotionArgs(/*downTime=*/0, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}});
-    args2.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
-    mBlocker->notifyMotion(&args2);
+    args2.pointerProperties[0].toolType = ToolType::STYLUS;
+    mBlocker->notifyMotion(args2);
     mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE));
 
     // Lift up the stylus. If it were a touch event, this would force the model to decide on whether
     // it's a palm.
     NotifyMotionArgs args3 =
             generateMotionArgs(/*downTime=*/0, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}});
-    args3.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
-    mBlocker->notifyMotion(&args3);
+    args3.pointerProperties[0].toolType = ToolType::STYLUS;
+    mBlocker->notifyMotion(args3);
     mTestListener.assertNotifyMotionWasCalled(WithMotionAction(UP));
 }
 
@@ -643,50 +621,50 @@
  * Stylus event should continue to work even after touch is detected as a palm.
  */
 TEST_F(UnwantedInteractionBlockerTest, TouchIsBlockedWhenMixedWithStylus) {
-    InputDeviceInfo info = generateTestDeviceInfo();
-    info.addSource(AINPUT_SOURCE_STYLUS);
-    mBlocker->notifyInputDevicesChanged({info});
+    NotifyInputDevicesChangedArgs deviceChangedArgs = {/*id=*/0, {generateTestDeviceInfo()}};
+    deviceChangedArgs.inputDeviceInfos[0].addSource(AINPUT_SOURCE_STYLUS);
+    mBlocker->notifyInputDevicesChanged(deviceChangedArgs);
 
     // Touch down
     NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}});
-    mBlocker->notifyMotion(&args1);
+    mBlocker->notifyMotion(args1);
     mTestListener.assertNotifyMotionWasCalled(WithMotionAction(DOWN));
 
     // Stylus pointer down
     NotifyMotionArgs args2 = generateMotionArgs(/*downTime=*/0, RESAMPLE_PERIOD, POINTER_1_DOWN,
                                                 {{1, 2, 3}, {10, 20, 30}});
-    args2.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
-    mBlocker->notifyMotion(&args2);
+    args2.pointerProperties[1].toolType = ToolType::STYLUS;
+    mBlocker->notifyMotion(args2);
     mTestListener.assertNotifyMotionWasCalled(WithMotionAction(POINTER_1_DOWN));
 
     // Large touch oval on the next finger move
     NotifyMotionArgs args3 = generateMotionArgs(/*downTime=*/0, 2 * RESAMPLE_PERIOD, MOVE,
                                                 {{1, 2, 300}, {11, 21, 30}});
-    args3.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
-    mBlocker->notifyMotion(&args3);
+    args3.pointerProperties[1].toolType = ToolType::STYLUS;
+    mBlocker->notifyMotion(args3);
     mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE));
 
     // Lift up the finger pointer. It should be canceled due to the heuristic filter.
     NotifyMotionArgs args4 = generateMotionArgs(/*downTime=*/0, 3 * RESAMPLE_PERIOD, POINTER_0_UP,
                                                 {{1, 2, 300}, {11, 21, 30}});
-    args4.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
-    mBlocker->notifyMotion(&args4);
+    args4.pointerProperties[1].toolType = ToolType::STYLUS;
+    mBlocker->notifyMotion(args4);
     mTestListener.assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(POINTER_0_UP), WithFlags(FLAG_CANCELED)));
 
     NotifyMotionArgs args5 =
             generateMotionArgs(/*downTime=*/0, 4 * RESAMPLE_PERIOD, MOVE, {{12, 22, 30}});
     args5.pointerProperties[0].id = args4.pointerProperties[1].id;
-    args5.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
-    mBlocker->notifyMotion(&args5);
+    args5.pointerProperties[0].toolType = ToolType::STYLUS;
+    mBlocker->notifyMotion(args5);
     mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE));
 
     // Lift up the stylus pointer
     NotifyMotionArgs args6 =
             generateMotionArgs(/*downTime=*/0, 5 * RESAMPLE_PERIOD, UP, {{4, 5, 200}});
     args6.pointerProperties[0].id = args4.pointerProperties[1].id;
-    args6.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
-    mBlocker->notifyMotion(&args6);
+    args6.pointerProperties[0].toolType = ToolType::STYLUS;
+    mBlocker->notifyMotion(args6);
     mTestListener.assertNotifyMotionWasCalled(WithMotionAction(UP));
 }
 
@@ -699,18 +677,16 @@
 TEST_F(UnwantedInteractionBlockerTestDeathTest, InconsistentEventAfterResetCausesACrash) {
     ScopedSilentDeath _silentDeath;
     NotifyMotionArgs args;
-    mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
-    mBlocker->notifyMotion(
-            &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}})));
-    mBlocker->notifyMotion(
-            &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}})));
+    mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
+    mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}}));
+    mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}}));
     NotifyDeviceResetArgs resetArgs(/*sequenceNum=*/1, /*eventTime=*/3, DEVICE_ID);
-    mBlocker->notifyDeviceReset(&resetArgs);
+    mBlocker->notifyDeviceReset(resetArgs);
     // Sending MOVE without a DOWN -> should crash!
     ASSERT_DEATH(
             {
-                mBlocker->notifyMotion(&(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/4,
-                                                                   MOVE, {{7, 8, 9}})));
+                mBlocker->notifyMotion(
+                        generateMotionArgs(/*downTime=*/0, /*eventTime=*/4, MOVE, {{7, 8, 9}}));
             },
             "Could not find slot");
 }
@@ -720,9 +696,13 @@
  */
 TEST_F(UnwantedInteractionBlockerTestDeathTest, WhenMoveWithoutDownCausesACrash) {
     ScopedSilentDeath _silentDeath;
-    NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 2, 3}});
-    mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
-    ASSERT_DEATH({ mBlocker->notifyMotion(&args); }, "Could not find slot");
+    mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
+    ASSERT_DEATH(
+            {
+                mBlocker->notifyMotion(
+                        generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 2, 3}}));
+            },
+            "Could not find slot");
 }
 
 class PalmRejectorTest : public testing::Test {
diff --git a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp
index be85026..8098ef2 100644
--- a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp
@@ -38,8 +38,8 @@
             std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
     FuzzContainer fuzzer(fdp);
 
-    CursorInputMapper& mapper = fuzzer.getMapper<CursorInputMapper>();
     auto policyConfig = fuzzer.getPolicyConfig();
+    CursorInputMapper& mapper = fuzzer.getMapper<CursorInputMapper>(policyConfig);
 
     // Loop through mapper operations until randomness is exhausted.
     while (fdp->remaining_bytes() > 0) {
@@ -52,15 +52,17 @@
                 [&]() -> void { mapper.getSources(); },
                 [&]() -> void {
                     std::list<NotifyArgs> unused =
-                            mapper.configure(fdp->ConsumeIntegral<nsecs_t>(), &policyConfig,
-                                             fdp->ConsumeIntegral<int32_t>());
+                            mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig,
+                                               InputReaderConfiguration::Change(
+                                                       fdp->ConsumeIntegral<int32_t>()));
                 },
                 [&]() -> void {
                     // Need to reconfigure with 0 or you risk a NPE.
                     std::list<NotifyArgs> unused =
-                            mapper.configure(fdp->ConsumeIntegral<nsecs_t>(), &policyConfig, 0);
+                            mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig,
+                                               InputReaderConfiguration::Change(0));
                     InputDeviceInfo info;
-                    mapper.populateDeviceInfo(&info);
+                    mapper.populateDeviceInfo(info);
                 },
                 [&]() -> void {
                     int32_t type, code;
@@ -71,7 +73,8 @@
 
                     // Need to reconfigure with 0 or you risk a NPE.
                     std::list<NotifyArgs> unused =
-                            mapper.configure(fdp->ConsumeIntegral<nsecs_t>(), &policyConfig, 0);
+                            mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig,
+                                               InputReaderConfiguration::Change(0));
                     RawEvent rawEvent{fdp->ConsumeIntegral<nsecs_t>(),
                                       fdp->ConsumeIntegral<nsecs_t>(),
                                       fdp->ConsumeIntegral<int32_t>(),
@@ -90,7 +93,8 @@
                 [&]() -> void {
                     // Need to reconfigure with 0 or you risk a NPE.
                     std::list<NotifyArgs> unused =
-                            mapper.configure(fdp->ConsumeIntegral<nsecs_t>(), &policyConfig, 0);
+                            mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig,
+                                               InputReaderConfiguration::Change(0));
                     mapper.getAssociatedDisplayId();
                 },
         })();
diff --git a/services/inputflinger/tests/fuzzers/FuzzContainer.h b/services/inputflinger/tests/fuzzers/FuzzContainer.h
index 76d2bcd..b992928 100644
--- a/services/inputflinger/tests/fuzzers/FuzzContainer.h
+++ b/services/inputflinger/tests/fuzzers/FuzzContainer.h
@@ -59,7 +59,7 @@
     void configureDevice() {
         nsecs_t arbitraryTime = mFdp->ConsumeIntegral<nsecs_t>();
         std::list<NotifyArgs> out;
-        out += mFuzzDevice->configure(arbitraryTime, &mPolicyConfig, 0);
+        out += mFuzzDevice->configure(arbitraryTime, mPolicyConfig, /*changes=*/{});
         out += mFuzzDevice->reset(arbitraryTime);
         for (const NotifyArgs& args : out) {
             mFuzzListener.notify(args);
@@ -75,9 +75,12 @@
 
     template <class T, typename... Args>
     T& getMapper(Args... args) {
-        T& mapper = mFuzzDevice->addMapper<T>(mFdp->ConsumeIntegral<int32_t>(), args...);
+        int32_t eventhubId = mFdp->ConsumeIntegral<int32_t>();
+        // ensure a device entry exists for this eventHubId
+        mFuzzDevice->addEmptyEventHubDevice(eventhubId);
         configureDevice();
-        return mapper;
+
+        return mFuzzDevice->template constructAndAddMapper<T>(eventhubId, args...);
     }
 };
 
diff --git a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
index 2909129..f8ebc97 100644
--- a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
@@ -28,7 +28,7 @@
     // Create a basic motion event for testing
     PointerProperties properties;
     properties.id = 0;
-    properties.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+    properties.toolType = getFuzzedToolType(fdp);
     PointerCoords coords;
     coords.clear();
     for (int32_t i = 0; i < fdp.ConsumeIntegralInRange<int32_t>(0, MAX_AXES); i++) {
@@ -67,51 +67,42 @@
         fdp.PickValueInArray<std::function<void()>>({
                 [&]() -> void {
                     // SendToNextStage_NotifyConfigurationChangedArgs
-                    NotifyConfigurationChangedArgs
-                            args(/*sequenceNum=*/fdp.ConsumeIntegral<uint32_t>(),
-                                 /*eventTime=*/fdp.ConsumeIntegral<nsecs_t>());
-                    mClassifier->notifyConfigurationChanged(&args);
+                    mClassifier->notifyConfigurationChanged(
+                            {/*sequenceNum=*/fdp.ConsumeIntegral<int32_t>(),
+                             /*eventTime=*/fdp.ConsumeIntegral<nsecs_t>()});
                 },
                 [&]() -> void {
                     // SendToNextStage_NotifyKeyArgs
                     const nsecs_t eventTime = fdp.ConsumeIntegral<nsecs_t>();
                     const nsecs_t readTime =
                             eventTime + fdp.ConsumeIntegralInRange<nsecs_t>(0, 1E8);
-                    NotifyKeyArgs keyArgs(/*sequenceNum=*/fdp.ConsumeIntegral<uint32_t>(),
-                                          eventTime, readTime,
-                                          /*deviceId=*/fdp.ConsumeIntegral<int32_t>(),
-                                          AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT,
-                                          /*policyFlags=*/fdp.ConsumeIntegral<uint32_t>(),
-                                          AKEY_EVENT_ACTION_DOWN,
-                                          /*flags=*/fdp.ConsumeIntegral<int32_t>(), AKEYCODE_HOME,
-                                          /*scanCode=*/fdp.ConsumeIntegral<int32_t>(), AMETA_NONE,
-                                          /*downTime=*/fdp.ConsumeIntegral<nsecs_t>());
-
-                    mClassifier->notifyKey(&keyArgs);
+                    mClassifier->notifyKey({/*sequenceNum=*/fdp.ConsumeIntegral<int32_t>(),
+                                            eventTime, readTime,
+                                            /*deviceId=*/fdp.ConsumeIntegral<int32_t>(),
+                                            AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT,
+                                            /*policyFlags=*/fdp.ConsumeIntegral<uint32_t>(),
+                                            AKEY_EVENT_ACTION_DOWN,
+                                            /*flags=*/fdp.ConsumeIntegral<int32_t>(), AKEYCODE_HOME,
+                                            /*scanCode=*/fdp.ConsumeIntegral<int32_t>(), AMETA_NONE,
+                                            /*downTime=*/fdp.ConsumeIntegral<nsecs_t>()});
                 },
                 [&]() -> void {
                     // SendToNextStage_NotifyMotionArgs
-                    NotifyMotionArgs motionArgs = generateFuzzedMotionArgs(fdp);
-                    mClassifier->notifyMotion(&motionArgs);
+                    mClassifier->notifyMotion(generateFuzzedMotionArgs(fdp));
                 },
                 [&]() -> void {
                     // SendToNextStage_NotifySwitchArgs
-                    NotifySwitchArgs switchArgs(/*sequenceNum=*/fdp.ConsumeIntegral<uint32_t>(),
-                                                /*eventTime=*/fdp.ConsumeIntegral<nsecs_t>(),
-                                                /*policyFlags=*/fdp.ConsumeIntegral<uint32_t>(),
-                                                /*switchValues=*/fdp.ConsumeIntegral<uint32_t>(),
-                                                /*switchMask=*/fdp.ConsumeIntegral<uint32_t>());
-
-                    mClassifier->notifySwitch(&switchArgs);
+                    mClassifier->notifySwitch({/*sequenceNum=*/fdp.ConsumeIntegral<int32_t>(),
+                                               /*eventTime=*/fdp.ConsumeIntegral<nsecs_t>(),
+                                               /*policyFlags=*/fdp.ConsumeIntegral<uint32_t>(),
+                                               /*switchValues=*/fdp.ConsumeIntegral<uint32_t>(),
+                                               /*switchMask=*/fdp.ConsumeIntegral<uint32_t>()});
                 },
                 [&]() -> void {
                     // SendToNextStage_NotifyDeviceResetArgs
-                    NotifyDeviceResetArgs resetArgs(
-                            /*sequenceNum=*/fdp.ConsumeIntegral<uint32_t>(),
-                            /*eventTime=*/fdp.ConsumeIntegral<nsecs_t>(),
-                            /*deviceId=*/fdp.ConsumeIntegral<int32_t>());
-
-                    mClassifier->notifyDeviceReset(&resetArgs);
+                    mClassifier->notifyDeviceReset({/*sequenceNum=*/fdp.ConsumeIntegral<int32_t>(),
+                                                    /*eventTime=*/fdp.ConsumeIntegral<nsecs_t>(),
+                                                    /*deviceId=*/fdp.ConsumeIntegral<int32_t>()});
                 },
                 [&]() -> void {
                     // InputClassifierConverterTest
diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
index 20242b1..9223287 100644
--- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
@@ -82,7 +82,7 @@
         return reader->hasKeys(deviceId, sourceMask, keyCodes, outFlags);
     }
 
-    void requestRefreshConfiguration(uint32_t changes) {
+    void requestRefreshConfiguration(ConfigurationChanges changes) {
         reader->requestRefreshConfiguration(changes);
     }
 
@@ -165,6 +165,10 @@
         return reader->getBluetoothAddress(deviceId);
     }
 
+    void sysfsNodeChanged(const std::string& sysfsNodePath) {
+        reader->sysfsNodeChanged(sysfsNodePath);
+    }
+
 private:
     std::unique_ptr<InputReaderInterface> reader;
 };
@@ -228,7 +232,8 @@
                                     fdp->ConsumeIntegral<uint32_t>(), keyCodes, outFlags.data());
                 },
                 [&]() -> void {
-                    reader->requestRefreshConfiguration(fdp->ConsumeIntegral<uint32_t>());
+                    reader->requestRefreshConfiguration(
+                            InputReaderConfiguration::Change(fdp->ConsumeIntegral<uint32_t>()));
                 },
                 [&]() -> void {
                     reader->cancelVibrate(fdp->ConsumeIntegral<int32_t>(),
diff --git a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp
index 8e2d677..616e870 100644
--- a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp
@@ -44,10 +44,10 @@
             std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
     FuzzContainer fuzzer(fdp);
 
-    KeyboardInputMapper& mapper =
-            fuzzer.getMapper<KeyboardInputMapper>(fdp->ConsumeIntegral<uint32_t>(),
-                                                  fdp->ConsumeIntegral<int32_t>());
     auto policyConfig = fuzzer.getPolicyConfig();
+    KeyboardInputMapper& mapper =
+            fuzzer.getMapper<KeyboardInputMapper>(policyConfig, fdp->ConsumeIntegral<uint32_t>(),
+                                                  fdp->ConsumeIntegral<int32_t>());
 
     // Loop through mapper operations until randomness is exhausted.
     while (fdp->remaining_bytes() > 0) {
@@ -59,13 +59,14 @@
                 },
                 [&]() -> void {
                     InputDeviceInfo info;
-                    mapper.populateDeviceInfo(&info);
+                    mapper.populateDeviceInfo(info);
                 },
                 [&]() -> void { mapper.getSources(); },
                 [&]() -> void {
                     std::list<NotifyArgs> unused =
-                            mapper.configure(fdp->ConsumeIntegral<nsecs_t>(), &policyConfig,
-                                             fdp->ConsumeIntegral<uint32_t>());
+                            mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig,
+                                               InputReaderConfiguration::Change(
+                                                       fdp->ConsumeIntegral<uint32_t>()));
                 },
                 [&]() -> void {
                     std::list<NotifyArgs> unused = mapper.reset(fdp->ConsumeIntegral<nsecs_t>());
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index 7c9be5c..1e44e0f 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -66,6 +66,14 @@
 
 namespace android {
 
+template<class Fdp>
+ToolType getFuzzedToolType(Fdp& fdp) {
+    const int32_t toolType = fdp.template ConsumeIntegralInRange<int32_t>(
+                            static_cast<int32_t>(ToolType::ftl_first),
+                            static_cast<int32_t>(ToolType::ftl_last));
+    return static_cast<ToolType>(toolType);
+}
+
 class FuzzEventHub : public EventHubInterface {
     InputDeviceIdentifier mIdentifier;
     std::vector<TouchVideoFrame> mVideoFrames;
@@ -86,8 +94,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 {
@@ -217,6 +225,7 @@
     bool isDeviceEnabled(int32_t deviceId) const override { return mFdp->ConsumeBool(); }
     status_t enableDevice(int32_t deviceId) override { return mFdp->ConsumeIntegral<status_t>(); }
     status_t disableDevice(int32_t deviceId) override { return mFdp->ConsumeIntegral<status_t>(); }
+    void sysfsNodeChanged(const std::string& sysfsNodePath) override {}
 };
 
 class FuzzPointerController : public PointerControllerInterface {
@@ -225,14 +234,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 {}
@@ -277,14 +293,15 @@
 
 class FuzzInputListener : public virtual InputListenerInterface {
 public:
-    void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override {}
-    void notifyKey(const NotifyKeyArgs* args) override {}
-    void notifyMotion(const NotifyMotionArgs* args) override {}
-    void notifySwitch(const NotifySwitchArgs* args) override {}
-    void notifySensor(const NotifySensorArgs* args) override{};
-    void notifyVibratorState(const NotifyVibratorStateArgs* args) override{};
-    void notifyDeviceReset(const NotifyDeviceResetArgs* args) override {}
-    void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override{};
+    void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override {}
+    void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override {}
+    void notifyKey(const NotifyKeyArgs& args) override {}
+    void notifyMotion(const NotifyMotionArgs& args) override {}
+    void notifySwitch(const NotifySwitchArgs& args) override {}
+    void notifySensor(const NotifySensorArgs& args) override{};
+    void notifyVibratorState(const NotifyVibratorStateArgs& args) override{};
+    void notifyDeviceReset(const NotifyDeviceResetArgs& args) override {}
+    void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override{};
 };
 
 class FuzzInputReaderContext : public InputReaderContext {
diff --git a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp
index 011455b..212462d 100644
--- a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp
@@ -61,8 +61,8 @@
             std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
     FuzzContainer fuzzer(fdp);
 
-    MultiTouchInputMapper& mapper = fuzzer.getMapper<MultiTouchInputMapper>();
     auto policyConfig = fuzzer.getPolicyConfig();
+    MultiTouchInputMapper& mapper = fuzzer.getMapper<MultiTouchInputMapper>(policyConfig);
 
     // Loop through mapper operations until randomness is exhausted.
     while (fdp->remaining_bytes() > 0) {
@@ -74,13 +74,14 @@
                 },
                 [&]() -> void {
                     InputDeviceInfo info;
-                    mapper.populateDeviceInfo(&info);
+                    mapper.populateDeviceInfo(info);
                 },
                 [&]() -> void { mapper.getSources(); },
                 [&]() -> void {
                     std::list<NotifyArgs> unused =
-                            mapper.configure(fdp->ConsumeIntegral<nsecs_t>(), &policyConfig,
-                                             fdp->ConsumeIntegral<uint32_t>());
+                            mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig,
+                                               InputReaderConfiguration::Change(
+                                                       fdp->ConsumeIntegral<uint32_t>()));
                 },
                 [&]() -> void {
                     std::list<NotifyArgs> unused = mapper.reset(fdp->ConsumeIntegral<nsecs_t>());
@@ -127,8 +128,7 @@
                 [&]() -> void {
                     StylusState state{fdp->ConsumeIntegral<nsecs_t>(),
                                       fdp->ConsumeFloatingPoint<float>(),
-                                      fdp->ConsumeIntegral<uint32_t>(),
-                                      fdp->ConsumeIntegral<int32_t>()};
+                                      fdp->ConsumeIntegral<uint32_t>(), getFuzzedToolType(*fdp)};
                     std::list<NotifyArgs> unused = mapper.updateExternalStylusState(state);
                 },
                 [&]() -> void { mapper.getAssociatedDisplayId(); },
diff --git a/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp
index c4938f2..590207e 100644
--- a/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp
@@ -24,8 +24,8 @@
             std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
     FuzzContainer fuzzer(fdp);
 
-    SwitchInputMapper& mapper = fuzzer.getMapper<SwitchInputMapper>();
     auto policyConfig = fuzzer.getPolicyConfig();
+    SwitchInputMapper& mapper = fuzzer.getMapper<SwitchInputMapper>(policyConfig);
 
     // Loop through mapper operations until randomness is exhausted.
     while (fdp->remaining_bytes() > 0) {
diff --git a/services/powermanager/Android.bp b/services/powermanager/Android.bp
index 7fb33e5..b34e54f 100644
--- a/services/powermanager/Android.bp
+++ b/services/powermanager/Android.bp
@@ -43,6 +43,14 @@
         "android.hardware.power-V4-cpp",
     ],
 
+    export_shared_lib_headers: [
+        "android.hardware.power@1.0",
+        "android.hardware.power@1.1",
+        "android.hardware.power@1.2",
+        "android.hardware.power@1.3",
+        "android.hardware.power-V4-cpp",
+    ],
+
     cflags: [
         "-Wall",
         "-Werror",
diff --git a/services/sensorservice/BatteryService.cpp b/services/sensorservice/BatteryService.cpp
index 94de55c..0ea548c 100644
--- a/services/sensorservice/BatteryService.cpp
+++ b/services/sensorservice/BatteryService.cpp
@@ -30,8 +30,8 @@
 namespace android {
 // ---------------------------------------------------------------------------
 
-BatteryService::BatteryService() : mBatteryStatService(nullptr) {
-}
+BatteryService::BatteryService()
+    : mBatteryStatService(nullptr), mLastWakeupSensorEventReportedMs(0) {}
 
 bool BatteryService::addSensor(uid_t uid, int handle) {
     Mutex::Autolock _l(mActivationsLock);
@@ -74,6 +74,14 @@
     }
 }
 
+void BatteryService::noteWakeupSensorEventImpl(int64_t elapsedNanos, uid_t uid, int handle) {
+    if (checkService()) {
+        int64_t identity = IPCThreadState::self()->clearCallingIdentity();
+        mBatteryStatService->noteWakeupSensorEvent(elapsedNanos, uid, handle);
+        IPCThreadState::self()->restoreCallingIdentity(identity);
+    }
+}
+
 bool BatteryService::checkService() {
     if (mBatteryStatService == nullptr) {
         const sp<IServiceManager> sm(defaultServiceManager());
diff --git a/services/sensorservice/BatteryService.h b/services/sensorservice/BatteryService.h
index 13fc58a..60ef03f 100644
--- a/services/sensorservice/BatteryService.h
+++ b/services/sensorservice/BatteryService.h
@@ -19,11 +19,14 @@
 
 #include <batterystats/IBatteryStats.h>
 #include <utils/Singleton.h>
+#include <utils/SortedVector.h>
+#include <utils/SystemClock.h>
 
 namespace android {
 // ---------------------------------------------------------------------------
 
 class BatteryService : public Singleton<BatteryService> {
+    static constexpr int64_t WAKEUP_SENSOR_EVENT_DEBOUNCE_MS = 1000;
 
     friend class Singleton<BatteryService>;
     sp<IBatteryStats> mBatteryStatService;
@@ -32,6 +35,7 @@
 
     void enableSensorImpl(uid_t uid, int handle);
     void disableSensorImpl(uid_t uid, int handle);
+    void noteWakeupSensorEventImpl(int64_t elapsedNanos, uid_t uid, int handle);
 
     struct Info {
         uid_t uid;
@@ -44,6 +48,7 @@
         }
     };
 
+    int64_t mLastWakeupSensorEventReportedMs;
     Mutex mActivationsLock;
     SortedVector<Info> mActivations;
     bool addSensor(uid_t uid, int handle);
@@ -57,6 +62,15 @@
     static void disableSensor(uid_t uid, int handle) {
         BatteryService::getInstance().disableSensorImpl(uid, handle);
     }
+    static void noteWakeupSensorEvent(int64_t elapsed, uid_t uid, int handle) {
+        BatteryService& instance = BatteryService::getInstance();
+        const int64_t nowElapsedMs = elapsedRealtime();
+        if (nowElapsedMs >= (instance.mLastWakeupSensorEventReportedMs
+                              + WAKEUP_SENSOR_EVENT_DEBOUNCE_MS)) {
+            instance.noteWakeupSensorEventImpl(elapsed, uid, handle);
+            instance.mLastWakeupSensorEventReportedMs = nowElapsedMs;
+        }
+    }
 };
 
 // ---------------------------------------------------------------------------
diff --git a/services/sensorservice/SensorEventConnection.cpp b/services/sensorservice/SensorEventConnection.cpp
index b94b1c0..dc5070c 100644
--- a/services/sensorservice/SensorEventConnection.cpp
+++ b/services/sensorservice/SensorEventConnection.cpp
@@ -23,6 +23,7 @@
 #include <sensor/SensorEventQueue.h>
 
 #include "vec.h"
+#include "BatteryService.h"
 #include "SensorEventConnection.h"
 #include "SensorDevice.h"
 
@@ -55,14 +56,13 @@
 SensorService::SensorEventConnection::~SensorEventConnection() {
     ALOGD_IF(DEBUG_CONNECTIONS, "~SensorEventConnection(%p)", this);
     destroy();
-    mService->cleanupConnection(this);
-    if (mEventCache != nullptr) {
-        delete[] mEventCache;
-    }
+    delete[] mEventCache;
 }
 
 void SensorService::SensorEventConnection::destroy() {
-    mDestroyed = true;
+    if (!mDestroyed.exchange(true)) {
+      mService->cleanupConnection(this);
+    }
 }
 
 void SensorService::SensorEventConnection::onFirstRef() {
@@ -392,6 +392,8 @@
     if (hasSensorAccess()) {
         index_wake_up_event = findWakeUpSensorEventLocked(scratch, count);
         if (index_wake_up_event >= 0) {
+            BatteryService::noteWakeupSensorEvent(scratch[index_wake_up_event].timestamp,
+                                                  mUid, scratch[index_wake_up_event].sensor);
             scratch[index_wake_up_event].flags |= WAKE_UP_SENSOR_EVENT_NEEDS_ACK;
             ++mWakeLockRefCount;
 #if DEBUG_CONNECTIONS
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index 0fb3cad..398d602 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -1648,7 +1648,7 @@
     int deviceId = c->getDeviceId();
     int sensorDeviceId = getDeviceIdFromHandle(sensorHandle);
     if (sensorDeviceId != c->getDeviceId()) {
-        ALOGE("Cannot configure direct channel created for device %d with a sensor that belongs"
+        ALOGE("Cannot configure direct channel created for device %d with a sensor that belongs "
               "to device %d", c->getDeviceId(), sensorDeviceId);
         return BAD_VALUE;
     }
diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h
index fe72a69..0aa1bcb 100644
--- a/services/sensorservice/SensorService.h
+++ b/services/sensorservice/SensorService.h
@@ -288,7 +288,7 @@
             void onUidStateChanged(uid_t uid __unused, int32_t procState __unused,
                                    int64_t procStateSeq __unused,
                                    int32_t capability __unused) override {}
-            void onUidProcAdjChanged(uid_t uid __unused) override {}
+            void onUidProcAdjChanged(uid_t uid __unused, int32_t adj __unused) override {}
 
             void addOverrideUid(uid_t uid, bool active);
             void removeOverrideUid(uid_t uid);
diff --git a/services/stats/Android.bp b/services/stats/Android.bp
index 7d358e1..6b99627 100644
--- a/services/stats/Android.bp
+++ b/services/stats/Android.bp
@@ -21,6 +21,7 @@
         "android.frameworks.stats@1.0",
         "android.frameworks.stats-V2-ndk",
         "libbinder_ndk",
+        "libexpresslog",
         "libhidlbase",
         "liblog",
         "libstatslog",
diff --git a/services/stats/StatsAidl.cpp b/services/stats/StatsAidl.cpp
index 0f01507..b22f903 100644
--- a/services/stats/StatsAidl.cpp
+++ b/services/stats/StatsAidl.cpp
@@ -22,6 +22,7 @@
 
 #include "StatsAidl.h"
 
+#include <Counter.h>
 #include <log/log.h>
 #include <stats_annotations.h>
 #include <stats_event.h>
@@ -29,11 +30,18 @@
 
 #include <unordered_map>
 
+namespace {
+    static const char* g_AtomErrorMetricName =
+        "statsd_errors.value_report_vendor_atom_errors_count";
+}
+
 namespace aidl {
 namespace android {
 namespace frameworks {
 namespace stats {
 
+using ::android::expresslog::Counter;
+
 template <typename E>
 constexpr typename std::underlying_type<E>::type to_underlying(E e) noexcept {
     return static_cast<typename std::underlying_type<E>::type>(e);
@@ -86,12 +94,14 @@
 ndk::ScopedAStatus StatsHal::reportVendorAtom(const VendorAtom& vendorAtom) {
     if (vendorAtom.atomId < 100000 || vendorAtom.atomId >= 200000) {
         ALOGE("Atom ID %ld is not a valid vendor atom ID", (long)vendorAtom.atomId);
+        Counter::logIncrement(g_AtomErrorMetricName);
         return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
                 -1, "Not a valid vendor atom ID");
     }
     if (vendorAtom.reverseDomainName.length() > 50) {
         ALOGE("Vendor atom reverse domain name %s is too long.",
               vendorAtom.reverseDomainName.c_str());
+        Counter::logIncrement(g_AtomErrorMetricName);
         return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
                 -1, "Vendor atom reverse domain name is too long");
     }
@@ -100,8 +110,9 @@
 
     if (vendorAtom.atomAnnotations) {
         if (!write_atom_annotations(event, *vendorAtom.atomAnnotations)) {
-            ALOGE("Atom ID %ld has incompatible atom level annotation", (long)vendorAtom.atomId);
             AStatsEvent_release(event);
+            ALOGE("Atom ID %ld has incompatible atom level annotation", (long)vendorAtom.atomId);
+            Counter::logIncrement(g_AtomErrorMetricName);
             return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
                     -1, "invalid atom annotation");
         }
@@ -222,6 +233,7 @@
             default: {
                 AStatsEvent_release(event);
                 ALOGE("Atom ID %ld has invalid atomValue.getTag", (long)vendorAtom.atomId);
+                Counter::logIncrement(g_AtomErrorMetricName);
                 return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
                         -1, "invalid atomValue.getTag");
                 break;
@@ -235,9 +247,10 @@
             VLOG("Atom ID %ld has %ld annotations for field #%ld", (long)vendorAtom.atomId,
                  (long)fieldAnnotations.size(), (long)atomValueIdx + 2);
             if (!write_field_annotations(event, fieldAnnotations)) {
+                AStatsEvent_release(event);
                 ALOGE("Atom ID %ld has incompatible field level annotation for field #%ld",
                       (long)vendorAtom.atomId, (long)atomValueIdx + 2);
-                AStatsEvent_release(event);
+                Counter::logIncrement(g_AtomErrorMetricName);
                 return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
                         -1, "invalid atom field annotation");
             }
@@ -249,6 +262,7 @@
     AStatsEvent_release(event);
     if (ret <= 0) {
         ALOGE("Error writing Atom ID %ld. Result: %d", (long)vendorAtom.atomId, ret);
+        Counter::logIncrement(g_AtomErrorMetricName);
     }
     return ret <= 0 ? ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(ret,
                                                                               "report atom failed")
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index fe7cff7..5683a92 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -47,8 +47,6 @@
         "android.hardware.graphics.composer@2.2",
         "android.hardware.graphics.composer@2.3",
         "android.hardware.graphics.composer@2.4",
-        "android.hardware.power@1.0",
-        "android.hardware.power@1.3",
         "android.hardware.power-V4-cpp",
         "libbase",
         "libbinder",
@@ -63,6 +61,7 @@
         "liblayers_proto",
         "liblog",
         "libnativewindow",
+        "libpowermanager",
         "libprocessgroup",
         "libprotobuf-cpp-lite",
         "libsync",
@@ -105,7 +104,7 @@
         "android.hardware.graphics.composer@2.2",
         "android.hardware.graphics.composer@2.3",
         "android.hardware.graphics.composer@2.4",
-        "android.hardware.power@1.3",
+        "libpowermanager",
         "libhidlbase",
         "libtimestats",
     ],
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
index a8322d8..d93e25e 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
@@ -83,12 +83,9 @@
     // If set, causes the dirty regions to flash with the delay
     std::optional<std::chrono::microseconds> devOptFlashDirtyRegionsDelay;
 
-    // The earliest time to send the present command to the HAL
-    std::chrono::steady_clock::time_point earliestPresentTime;
-
-    // The previous present fence. Used together with earliestPresentTime
-    // to prevent an early presentation of a frame.
-    std::shared_ptr<FenceTime> previousPresentFence;
+    // Optional.
+    // The earliest time to send the present command to the HAL.
+    std::optional<std::chrono::steady_clock::time_point> earliestPresentTime;
 
     // The expected time for the next present
     nsecs_t expectedPresentTime{0};
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
index 608c53a..ccff1ec 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
@@ -19,6 +19,7 @@
 #include <optional>
 #include <ostream>
 #include <unordered_set>
+#include "ui/LayerStack.h"
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic push
@@ -140,7 +141,7 @@
             ClientCompositionTargetSettings&) const = 0;
 
     // Called after the layer is displayed to update the presentation fence
-    virtual void onLayerDisplayed(ftl::SharedFuture<FenceResult>) = 0;
+    virtual void onLayerDisplayed(ftl::SharedFuture<FenceResult>, ui::LayerStack layerStack) = 0;
 
     // Gets some kind of identifier for the layer for debug purposes.
     virtual const char* getDebugName() const = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
index eae5871..35ca3a5 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
@@ -210,8 +210,8 @@
     // The dimming flag
     bool dimmingEnabled{true};
 
-    float currentSdrHdrRatio = 1.f;
-    float desiredSdrHdrRatio = 1.f;
+    float currentHdrSdrRatio = 1.f;
+    float desiredHdrSdrRatio = 1.f;
 
     gui::CachingHint cachingHint = gui::CachingHint::Enabled;
     virtual ~LayerFECompositionState();
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
index c291652..a3fda61 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
@@ -122,12 +122,9 @@
 
     bool previousDeviceRequestedSuccess = false;
 
+    // Optional.
     // The earliest time to send the present command to the HAL
-    std::chrono::steady_clock::time_point earliestPresentTime;
-
-    // The previous present fence. Used together with earliestPresentTime
-    // to prevent an early presentation of a frame.
-    std::shared_ptr<FenceTime> previousPresentFence;
+    std::optional<std::chrono::steady_clock::time_point> earliestPresentTime;
 
     // The expected time for the next present
     nsecs_t expectedPresentTime{0};
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
index d5c488e..ce2b96f 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
@@ -247,6 +247,10 @@
 
     ui::Dataspace getDataspace() const { return mOutputDataspace.get(); }
 
+    float getHdrSdrRatio() const {
+        return getOutputLayer()->getLayerFE().getCompositionState()->currentHdrSdrRatio;
+    };
+
     wp<GraphicBuffer> getBuffer() const { return mBuffer.get(); }
 
     bool isProtected() const { return mIsProtected.get(); }
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
index 12e063b..15e4577 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
@@ -49,7 +49,8 @@
                        std::optional<compositionengine::LayerFE::LayerSettings>(
                                compositionengine::LayerFE::ClientCompositionTargetSettings&));
 
-    MOCK_METHOD(void, onLayerDisplayed, (ftl::SharedFuture<FenceResult>), (override));
+    MOCK_METHOD(void, onLayerDisplayed, (ftl::SharedFuture<FenceResult>, ui::LayerStack),
+                (override));
 
     MOCK_CONST_METHOD0(getDebugName, const char*());
     MOCK_CONST_METHOD0(getSequence, int32_t());
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index d50a768..85fc095 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -263,7 +263,6 @@
     if (status_t result =
                 hwc.getDeviceCompositionChanges(*halDisplayId, requiresClientComposition,
                                                 getState().earliestPresentTime,
-                                                getState().previousPresentFence,
                                                 getState().expectedPresentTime, outChanges);
         result != NO_ERROR) {
         ALOGE("chooseCompositionStrategy failed for %s: %d (%s)", getName().c_str(), result,
@@ -380,16 +379,11 @@
 
     const TimePoint startTime = TimePoint::now();
 
-    if (isPowerHintSessionEnabled()) {
-        if (!getCompositionEngine().getHwComposer().getComposer()->isSupported(
-                    Hwc2::Composer::OptionalFeature::ExpectedPresentTime) &&
-            getState().previousPresentFence->getSignalTime() != Fence::SIGNAL_TIME_PENDING) {
-            mPowerAdvisor->setHwcPresentDelayedTime(mId, getState().earliestPresentTime);
-        }
+    if (isPowerHintSessionEnabled() && getState().earliestPresentTime) {
+        mPowerAdvisor->setHwcPresentDelayedTime(mId, *getState().earliestPresentTime);
     }
 
-    hwc.presentAndGetReleaseFences(*halDisplayIdOpt, getState().earliestPresentTime,
-                                   getState().previousPresentFence);
+    hwc.presentAndGetReleaseFences(*halDisplayIdOpt, getState().earliestPresentTime);
 
     if (isPowerHintSessionEnabled()) {
         mPowerAdvisor->setHwcPresentTiming(mId, startTime, TimePoint::now());
diff --git a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
index 615d04b..426cc57 100644
--- a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
@@ -121,9 +121,9 @@
     dumpVal(out, "dataspace", toString(dataspace), dataspace);
     dumpVal(out, "hdr metadata types", hdrMetadata.validTypes);
     dumpVal(out, "dimming enabled", dimmingEnabled);
-    if (currentSdrHdrRatio > 1.01f || desiredSdrHdrRatio > 1.01f) {
-        dumpVal(out, "current sdr/hdr ratio", currentSdrHdrRatio);
-        dumpVal(out, "desired sdr/hdr ratio", desiredSdrHdrRatio);
+    if (currentHdrSdrRatio > 1.01f || desiredHdrSdrRatio > 1.01f) {
+        dumpVal(out, "current hdr/sdr ratio", currentHdrSdrRatio);
+        dumpVal(out, "desired hdr/sdr ratio", desiredHdrSdrRatio);
     }
     dumpVal(out, "colorTransform", colorTransform);
     dumpVal(out, "caching hint", toString(cachingHint));
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 175dd1d..793959c 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -842,7 +842,6 @@
     }
 
     editState().earliestPresentTime = refreshArgs.earliestPresentTime;
-    editState().previousPresentFence = refreshArgs.previousPresentFence;
     editState().expectedPresentTime = refreshArgs.expectedPresentTime;
 
     compositionengine::OutputLayer* peekThroughLayer = nullptr;
@@ -1557,8 +1556,9 @@
             releaseFence =
                     Fence::merge("LayerRelease", releaseFence, frame.clientTargetAcquireFence);
         }
-        layer->getLayerFE().onLayerDisplayed(
-                ftl::yield<FenceResult>(std::move(releaseFence)).share());
+        layer->getLayerFE()
+                .onLayerDisplayed(ftl::yield<FenceResult>(std::move(releaseFence)).share(),
+                                  outputState.layerFilter.layerStack);
     }
 
     // We've got a list of layers needing fences, that are disjoint with
@@ -1566,7 +1566,8 @@
     // supply them with the present fence.
     for (auto& weakLayer : mReleasedLayers) {
         if (const auto layer = weakLayer.promote()) {
-            layer->onLayerDisplayed(ftl::yield<FenceResult>(frame.presentFence).share());
+            layer->onLayerDisplayed(ftl::yield<FenceResult>(frame.presentFence).share(),
+                                    outputState.layerFilter.layerStack);
         }
     }
 
@@ -1575,9 +1576,10 @@
 }
 
 void Output::renderCachedSets(const CompositionRefreshArgs& refreshArgs) {
-    if (mPlanner) {
-        mPlanner->renderCachedSets(getState(), refreshArgs.scheduledFrameTime,
-                                   getState().usesDeviceComposition || getSkipColorTransform());
+    const auto& outputState = getState();
+    if (mPlanner && outputState.isEnabled) {
+        mPlanner->renderCachedSets(outputState, refreshArgs.scheduledFrameTime,
+                                   outputState.usesDeviceComposition || getSkipColorTransform());
     }
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index 1b86cd3..0ac0ecb 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -344,8 +344,8 @@
         // RANGE_EXTENDED can "self-promote" to HDR, but is still rendered for a particular
         // range that we may need to re-adjust to the current display conditions
         if ((state.dataspace & HAL_DATASPACE_RANGE_MASK) == HAL_DATASPACE_RANGE_EXTENDED &&
-            layerFEState->currentSdrHdrRatio > 1.01f) {
-            layerBrightnessNits *= layerFEState->currentSdrHdrRatio;
+            layerFEState->currentHdrSdrRatio > 1.01f) {
+            layerBrightnessNits *= layerFEState->currentHdrSdrRatio;
         }
         state.dimmingRatio =
                 std::clamp(layerBrightnessNits / getOutput().getState().displayBrightnessNits, 0.f,
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
index a00ce57..8ced0ac 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -378,6 +378,10 @@
             // to avoid flickering/color differences.
             return true;
         }
+        // TODO(b/274804887): temp fix of overdimming issue, skip caching if hsdr/sdr ratio > 1.01f
+        if (layer.getState()->getHdrSdrRatio() > 1.01f) {
+            return true;
+        }
         return false;
     });
 }
diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
index 0756c1b..9be6bc2 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
@@ -595,7 +595,7 @@
 TEST_F(DisplayChooseCompositionStrategyTest, takesEarlyOutOnHwcError) {
     EXPECT_CALL(*mDisplay, anyLayersRequireClientComposition()).WillOnce(Return(false));
     EXPECT_CALL(mHwComposer,
-                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), false, _, _, _, _))
+                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), false, _, _, _))
             .WillOnce(Return(INVALID_OPERATION));
 
     chooseCompositionStrategy(mDisplay.get());
@@ -619,8 +619,8 @@
             .WillOnce(Return(false));
 
     EXPECT_CALL(mHwComposer,
-                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _, _))
-            .WillOnce(testing::DoAll(testing::SetArgPointee<5>(mDeviceRequestedChanges),
+                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _))
+            .WillOnce(testing::DoAll(testing::SetArgPointee<4>(mDeviceRequestedChanges),
                                      Return(NO_ERROR)));
     EXPECT_CALL(*mDisplay, applyChangedTypesToLayers(mDeviceRequestedChanges.changedTypes))
             .Times(1);
@@ -672,8 +672,8 @@
             .WillOnce(Return(false));
 
     EXPECT_CALL(mHwComposer,
-                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _, _))
-            .WillOnce(DoAll(SetArgPointee<5>(mDeviceRequestedChanges), Return(NO_ERROR)));
+                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _))
+            .WillOnce(DoAll(SetArgPointee<4>(mDeviceRequestedChanges), Return(NO_ERROR)));
     EXPECT_CALL(*mDisplay, applyChangedTypesToLayers(mDeviceRequestedChanges.changedTypes))
             .Times(1);
     EXPECT_CALL(*mDisplay, applyDisplayRequests(mDeviceRequestedChanges.displayRequests)).Times(1);
@@ -901,7 +901,7 @@
     sp<Fence> layer1Fence = sp<Fence>::make();
     sp<Fence> layer2Fence = sp<Fence>::make();
 
-    EXPECT_CALL(mHwComposer, presentAndGetReleaseFences(HalDisplayId(DEFAULT_DISPLAY_ID), _, _))
+    EXPECT_CALL(mHwComposer, presentAndGetReleaseFences(HalDisplayId(DEFAULT_DISPLAY_ID), _))
             .Times(1);
     EXPECT_CALL(mHwComposer, getPresentFence(HalDisplayId(DEFAULT_DISPLAY_ID)))
             .WillOnce(Return(presentFence));
@@ -1078,7 +1078,7 @@
 
     mDisplay->editState().isEnabled = true;
 
-    EXPECT_CALL(mHwComposer, presentAndGetReleaseFences(_, _, _));
+    EXPECT_CALL(mHwComposer, presentAndGetReleaseFences(_, _));
     EXPECT_CALL(*mDisplaySurface, onFrameCommitted());
 
     mDisplay->postFramebuffer();
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 1a56ab7..67b94ee 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -54,16 +54,14 @@
     MOCK_METHOD2(allocatePhysicalDisplay, void(hal::HWDisplayId, PhysicalDisplayId));
 
     MOCK_METHOD1(createLayer, std::shared_ptr<HWC2::Layer>(HalDisplayId));
-    MOCK_METHOD6(getDeviceCompositionChanges,
-                 status_t(HalDisplayId, bool, std::chrono::steady_clock::time_point,
-                          const std::shared_ptr<FenceTime>&, nsecs_t,
-                          std::optional<android::HWComposer::DeviceRequestedChanges>*));
+    MOCK_METHOD5(getDeviceCompositionChanges,
+                 status_t(HalDisplayId, bool, std::optional<std::chrono::steady_clock::time_point>,
+                          nsecs_t, std::optional<android::HWComposer::DeviceRequestedChanges>*));
     MOCK_METHOD5(setClientTarget,
                  status_t(HalDisplayId, uint32_t, const sp<Fence>&, const sp<GraphicBuffer>&,
                           ui::Dataspace));
-    MOCK_METHOD3(presentAndGetReleaseFences,
-                 status_t(HalDisplayId, std::chrono::steady_clock::time_point,
-                          const std::shared_ptr<FenceTime>&));
+    MOCK_METHOD2(presentAndGetReleaseFences,
+                 status_t(HalDisplayId, std::optional<std::chrono::steady_clock::time_point>));
     MOCK_METHOD2(setPowerMode, status_t(PhysicalDisplayId, hal::PowerMode));
     MOCK_METHOD2(setActiveConfig, status_t(HalDisplayId, size_t));
     MOCK_METHOD2(setColorTransform, status_t(HalDisplayId, const mat4&));
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
index c555b39..961ec80 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
@@ -37,11 +37,10 @@
     MOCK_METHOD(void, notifyDisplayUpdateImminentAndCpuReset, (), (override));
     MOCK_METHOD(bool, usePowerHintSession, (), (override));
     MOCK_METHOD(bool, supportsPowerHintSession, (), (override));
-    MOCK_METHOD(bool, isPowerHintSessionRunning, (), (override));
-    MOCK_METHOD(void, setTargetWorkDuration, (Duration targetDuration), (override));
-    MOCK_METHOD(void, sendActualWorkDuration, (), (override));
-    MOCK_METHOD(void, sendPredictedWorkDuration, (), (override));
-    MOCK_METHOD(void, enablePowerHint, (bool enabled), (override));
+    MOCK_METHOD(bool, ensurePowerHintSessionRunning, (), (override));
+    MOCK_METHOD(void, updateTargetWorkDuration, (Duration targetDuration), (override));
+    MOCK_METHOD(void, reportActualWorkDuration, (), (override));
+    MOCK_METHOD(void, enablePowerHintSession, (bool enabled), (override));
     MOCK_METHOD(bool, startPowerHintSession, (const std::vector<int32_t>& threadIds), (override));
     MOCK_METHOD(void, setGpuFenceTime,
                 (DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime), (override));
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index aaf0f06..9e0e7b5 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -3220,16 +3220,19 @@
     // are passed. This happens to work with the current implementation, but
     // would not survive certain calls like Fence::merge() which would return a
     // new instance.
-    EXPECT_CALL(*mLayer1.layerFE, onLayerDisplayed(_))
-            .WillOnce([&layer1Fence](ftl::SharedFuture<FenceResult> futureFenceResult) {
+    EXPECT_CALL(*mLayer1.layerFE, onLayerDisplayed(_, _))
+            .WillOnce([&layer1Fence](ftl::SharedFuture<FenceResult> futureFenceResult,
+                                     ui::LayerStack) {
                 EXPECT_EQ(FenceResult(layer1Fence), futureFenceResult.get());
             });
-    EXPECT_CALL(*mLayer2.layerFE, onLayerDisplayed(_))
-            .WillOnce([&layer2Fence](ftl::SharedFuture<FenceResult> futureFenceResult) {
+    EXPECT_CALL(*mLayer2.layerFE, onLayerDisplayed(_, _))
+            .WillOnce([&layer2Fence](ftl::SharedFuture<FenceResult> futureFenceResult,
+                                     ui::LayerStack) {
                 EXPECT_EQ(FenceResult(layer2Fence), futureFenceResult.get());
             });
-    EXPECT_CALL(*mLayer3.layerFE, onLayerDisplayed(_))
-            .WillOnce([&layer3Fence](ftl::SharedFuture<FenceResult> futureFenceResult) {
+    EXPECT_CALL(*mLayer3.layerFE, onLayerDisplayed(_, _))
+            .WillOnce([&layer3Fence](ftl::SharedFuture<FenceResult> futureFenceResult,
+                                     ui::LayerStack) {
                 EXPECT_EQ(FenceResult(layer3Fence), futureFenceResult.get());
             });
 
@@ -3285,16 +3288,19 @@
     EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
 
     // Each released layer should be given the presentFence.
-    EXPECT_CALL(*releasedLayer1, onLayerDisplayed(_))
-            .WillOnce([&presentFence](ftl::SharedFuture<FenceResult> futureFenceResult) {
+    EXPECT_CALL(*releasedLayer1, onLayerDisplayed(_, _))
+            .WillOnce([&presentFence](ftl::SharedFuture<FenceResult> futureFenceResult,
+                                      ui::LayerStack) {
                 EXPECT_EQ(FenceResult(presentFence), futureFenceResult.get());
             });
-    EXPECT_CALL(*releasedLayer2, onLayerDisplayed(_))
-            .WillOnce([&presentFence](ftl::SharedFuture<FenceResult> futureFenceResult) {
+    EXPECT_CALL(*releasedLayer2, onLayerDisplayed(_, _))
+            .WillOnce([&presentFence](ftl::SharedFuture<FenceResult> futureFenceResult,
+                                      ui::LayerStack) {
                 EXPECT_EQ(FenceResult(presentFence), futureFenceResult.get());
             });
-    EXPECT_CALL(*releasedLayer3, onLayerDisplayed(_))
-            .WillOnce([&presentFence](ftl::SharedFuture<FenceResult> futureFenceResult) {
+    EXPECT_CALL(*releasedLayer3, onLayerDisplayed(_, _))
+            .WillOnce([&presentFence](ftl::SharedFuture<FenceResult> futureFenceResult,
+                                      ui::LayerStack) {
                 EXPECT_EQ(FenceResult(presentFence), futureFenceResult.get());
             });
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
index ca5ba69..bd030d0 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
@@ -662,6 +662,26 @@
     EXPECT_FALSE(cachedSet.requiresHolePunch());
 }
 
+TEST_F(CachedSetTest, holePunch_requiresNonHdrWithExtendedBrightness) {
+    const auto dataspace = static_cast<ui::Dataspace>(ui::Dataspace::STANDARD_DCI_P3 |
+                                                      ui::Dataspace::TRANSFER_SRGB |
+                                                      ui::Dataspace::RANGE_EXTENDED);
+    mTestLayers[0]->outputLayerCompositionState.dataspace = dataspace;
+    mTestLayers[0]->layerFECompositionState.currentHdrSdrRatio = 5.f;
+    mTestLayers[0]->layerState->update(&mTestLayers[0]->outputLayer);
+
+    CachedSet::Layer& layer = *mTestLayers[0]->cachedSetLayer.get();
+    auto& layerFECompositionState = mTestLayers[0]->layerFECompositionState;
+    layerFECompositionState.buffer = sp<GraphicBuffer>::make();
+    layerFECompositionState.blendMode = hal::BlendMode::NONE;
+    sp<mock::LayerFE> layerFE = mTestLayers[0]->layerFE;
+
+    CachedSet cachedSet(layer);
+    EXPECT_CALL(*layerFE, hasRoundedCorners()).WillRepeatedly(Return(true));
+
+    EXPECT_FALSE(cachedSet.requiresHolePunch());
+}
+
 TEST_F(CachedSetTest, holePunch_requiresNoBlending) {
     CachedSet::Layer& layer = *mTestLayers[0]->cachedSetLayer.get();
     auto& layerFECompositionState = mTestLayers[0]->layerFECompositionState;
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 3cdb3d5..f6ca9e2 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -50,8 +50,6 @@
 
 namespace hal = hardware::graphics::composer::hal;
 
-ui::Transform::RotationFlags DisplayDevice::sPrimaryDisplayRotationFlags = ui::Transform::ROT_0;
-
 DisplayDeviceCreationArgs::DisplayDeviceCreationArgs(
         const sp<SurfaceFlinger>& flinger, HWComposer& hwComposer, const wp<IBinder>& displayToken,
         std::shared_ptr<compositionengine::Display> compositionDisplay)
@@ -167,6 +165,7 @@
             .receivesInput = receivesInput(),
             .isSecure = isSecure(),
             .isPrimary = isPrimary(),
+            .isVirtual = isVirtual(),
             .rotationFlags = ui::Transform::toRotationFlags(mOrientation),
             .transformHint = getTransformHint()};
 }
@@ -181,11 +180,23 @@
         getCompositionDisplay()->applyDisplayBrightness(true);
     }
 
-    mPowerMode = mode;
+    if (mPowerMode) {
+        *mPowerMode = mode;
+    } else {
+        mPowerMode.emplace("PowerMode -" + to_string(getId()), mode);
+    }
 
     getCompositionDisplay()->setCompositionEnabled(isPoweredOn());
 }
 
+void DisplayDevice::tracePowerMode() {
+    // assign the same value for tracing
+    if (mPowerMode) {
+        const hal::PowerMode powerMode = *mPowerMode;
+        *mPowerMode = powerMode;
+    }
+}
+
 void DisplayDevice::enableLayerCaching(bool enable) {
     getCompositionDisplay()->setLayerCachingEnabled(enable);
 }
@@ -269,10 +280,6 @@
                                   Rect orientedDisplaySpaceRect) {
     mOrientation = orientation;
 
-    if (isPrimary()) {
-        sPrimaryDisplayRotationFlags = ui::Transform::toRotationFlags(orientation);
-    }
-
     // We need to take care of display rotation for globalTransform for case if the panel is not
     // installed aligned with device orientation.
     const auto transformOrientation = orientation + mPhysicalOrientation;
@@ -313,10 +320,6 @@
     return mStagedBrightness;
 }
 
-ui::Transform::RotationFlags DisplayDevice::getPrimaryDisplayRotationFlags() {
-    return sPrimaryDisplayRotationFlags;
-}
-
 void DisplayDevice::dump(utils::Dumper& dumper) const {
     using namespace std::string_view_literals;
 
@@ -522,8 +525,8 @@
 }
 
 void DisplayDevice::adjustRefreshRate(Fps pacesetterDisplayRefreshRate) {
-    using fps_approx_ops::operator==;
-    if (mRequestedRefreshRate == 0_Hz) {
+    using fps_approx_ops::operator<=;
+    if (mRequestedRefreshRate <= 0_Hz) {
         return;
     }
 
@@ -534,7 +537,12 @@
     }
 
     unsigned divisor = static_cast<unsigned>(
-            std::round(pacesetterDisplayRefreshRate.getValue() / mRequestedRefreshRate.getValue()));
+            std::floor(pacesetterDisplayRefreshRate.getValue() / mRequestedRefreshRate.getValue()));
+    if (divisor == 0) {
+        mAdjustedRefreshRate = 0_Hz;
+        return;
+    }
+
     mAdjustedRefreshRate = pacesetterDisplayRefreshRate / divisor;
 }
 
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index d9c3e1c..dc5f8a8 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -109,8 +109,6 @@
     ui::Rotation getPhysicalOrientation() const { return mPhysicalOrientation; }
     ui::Rotation getOrientation() const { return mOrientation; }
 
-    static ui::Transform::RotationFlags getPrimaryDisplayRotationFlags();
-
     std::optional<float> getStagedBrightness() const REQUIRES(kMainThreadContext);
     ui::Transform::RotationFlags getTransformHint() const;
     const ui::Transform& getTransform() const;
@@ -175,6 +173,7 @@
     std::optional<hardware::graphics::composer::hal::PowerMode> getPowerMode() const;
     void setPowerMode(hardware::graphics::composer::hal::PowerMode mode);
     bool isPoweredOn() const;
+    void tracePowerMode();
 
     // Enables layer caching on this DisplayDevice
     void enableLayerCaching(bool enable);
@@ -273,10 +272,9 @@
     const ui::Rotation mPhysicalOrientation;
     ui::Rotation mOrientation = ui::ROTATION_0;
 
-    static ui::Transform::RotationFlags sPrimaryDisplayRotationFlags;
-
     // Allow nullopt as initial power mode.
-    std::optional<hardware::graphics::composer::hal::PowerMode> mPowerMode;
+    using TracedPowerMode = TracedOrdinal<hardware::graphics::composer::hal::PowerMode>;
+    std::optional<TracedPowerMode> mPowerMode;
 
     std::optional<float> mStagedBrightness;
     std::optional<float> mBrightness;
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index bd2680f..f7049b9 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -329,14 +329,6 @@
     }
 }
 
-void AidlComposer::resetCommands(Display display) {
-    mMutex.lock_shared();
-    if (auto writer = getWriter(display)) {
-        writer->get().reset();
-    }
-    mMutex.unlock_shared();
-}
-
 Error AidlComposer::executeCommands(Display display) {
     mMutex.lock_shared();
     auto error = execute(display);
@@ -1054,9 +1046,8 @@
         return Error::BAD_DISPLAY;
     }
 
-    const auto& commands = writer->get().getPendingCommands();
+    auto commands = writer->get().takePendingCommands();
     if (commands.empty()) {
-        writer->get().reset();
         return Error::NONE;
     }
 
@@ -1088,8 +1079,6 @@
         }
     }
 
-    writer->get().reset();
-
     return error;
 }
 
@@ -1547,6 +1536,8 @@
 }
 
 bool AidlComposer::hasMultiThreadedPresentSupport(Display display) {
+#if 0
+    // TODO (b/259132483): Reenable
     const auto displayId = translate<int64_t>(display);
     std::vector<AidlDisplayCapability> capabilities;
     const auto status = mAidlComposerClient->getDisplayCapabilities(displayId, &capabilities);
@@ -1556,6 +1547,10 @@
     }
     return std::find(capabilities.begin(), capabilities.end(),
                      AidlDisplayCapability::MULTI_THREADED_PRESENT) != capabilities.end();
+#else
+    (void) display;
+    return false;
+#endif
 }
 
 void AidlComposer::addReader(Display display) {
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
index 8313c09..ce05b38 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -72,10 +72,6 @@
 
     void registerCallback(HWC2::ComposerCallback& callback) override;
 
-    // Reset all pending commands in the command buffer. Useful if you want to
-    // skip a frame but have already queued some commands.
-    void resetCommands(Display) override;
-
     // Explicitly flush all pending commands in the command buffer.
     Error executeCommands(Display) override;
 
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index c65c572..cf67795 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -110,10 +110,6 @@
 
     virtual void registerCallback(HWC2::ComposerCallback& callback) = 0;
 
-    // Reset all pending commands in the command buffer. Useful if you want to
-    // skip a frame but have already queued some commands.
-    virtual void resetCommands(Display) = 0;
-
     // Explicitly flush all pending commands in the command buffer.
     virtual Error executeCommands(Display) = 0;
 
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 28148ac..f350eba 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -395,8 +395,8 @@
 
 status_t HWComposer::getDeviceCompositionChanges(
         HalDisplayId displayId, bool frameUsesClientComposition,
-        std::chrono::steady_clock::time_point earliestPresentTime,
-        const std::shared_ptr<FenceTime>& previousPresentFence, nsecs_t expectedPresentTime,
+        std::optional<std::chrono::steady_clock::time_point> earliestPresentTime,
+        nsecs_t expectedPresentTime,
         std::optional<android::HWComposer::DeviceRequestedChanges>* outChanges) {
     ATRACE_CALL();
 
@@ -426,14 +426,13 @@
 
         // If composer supports getting the expected present time, we can skip
         // as composer will make sure to prevent early presentation
-        if (mComposer->isSupported(Hwc2::Composer::OptionalFeature::ExpectedPresentTime)) {
+        if (!earliestPresentTime) {
             return true;
         }
 
         // composer doesn't support getting the expected present time. We can only
         // skip validate if we know that we are not going to present early.
-        return std::chrono::steady_clock::now() >= earliestPresentTime ||
-                previousPresentFence->getSignalTime() == Fence::SIGNAL_TIME_PENDING;
+        return std::chrono::steady_clock::now() >= *earliestPresentTime;
     }();
 
     displayData.validateWasSkipped = false;
@@ -508,8 +507,8 @@
 }
 
 status_t HWComposer::presentAndGetReleaseFences(
-        HalDisplayId displayId, std::chrono::steady_clock::time_point earliestPresentTime,
-        const std::shared_ptr<FenceTime>& previousPresentFence) {
+        HalDisplayId displayId,
+        std::optional<std::chrono::steady_clock::time_point> earliestPresentTime) {
     ATRACE_CALL();
 
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
@@ -525,13 +524,9 @@
         return NO_ERROR;
     }
 
-    const bool waitForEarliestPresent =
-            !mComposer->isSupported(Hwc2::Composer::OptionalFeature::ExpectedPresentTime) &&
-            previousPresentFence->getSignalTime() != Fence::SIGNAL_TIME_PENDING;
-
-    if (waitForEarliestPresent) {
+    if (earliestPresentTime) {
         ATRACE_NAME("wait for earliest present time");
-        std::this_thread::sleep_until(earliestPresentTime);
+        std::this_thread::sleep_until(*earliestPresentTime);
     }
 
     auto error = hwcDisplay->present(&displayData.lastPresentFence);
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 7a3f41c..3702c62 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -143,17 +143,16 @@
     // expected.
     virtual status_t getDeviceCompositionChanges(
             HalDisplayId, bool frameUsesClientComposition,
-            std::chrono::steady_clock::time_point earliestPresentTime,
-            const std::shared_ptr<FenceTime>& previousPresentFence, nsecs_t expectedPresentTime,
-            std::optional<DeviceRequestedChanges>* outChanges) = 0;
+            std::optional<std::chrono::steady_clock::time_point> earliestPresentTime,
+            nsecs_t expectedPresentTime, std::optional<DeviceRequestedChanges>* outChanges) = 0;
 
     virtual status_t setClientTarget(HalDisplayId, uint32_t slot, const sp<Fence>& acquireFence,
                                      const sp<GraphicBuffer>& target, ui::Dataspace) = 0;
 
     // Present layers to the display and read releaseFences.
     virtual status_t presentAndGetReleaseFences(
-            HalDisplayId, std::chrono::steady_clock::time_point earliestPresentTime,
-            const std::shared_ptr<FenceTime>& previousPresentFence) = 0;
+            HalDisplayId,
+            std::optional<std::chrono::steady_clock::time_point> earliestPresentTime) = 0;
 
     // set power mode
     virtual status_t setPowerMode(PhysicalDisplayId, hal::PowerMode) = 0;
@@ -339,8 +338,8 @@
 
     status_t getDeviceCompositionChanges(
             HalDisplayId, bool frameUsesClientComposition,
-            std::chrono::steady_clock::time_point earliestPresentTime,
-            const std::shared_ptr<FenceTime>& previousPresentFence, nsecs_t expectedPresentTime,
+            std::optional<std::chrono::steady_clock::time_point> earliestPresentTime,
+            nsecs_t expectedPresentTime,
             std::optional<DeviceRequestedChanges>* outChanges) override;
 
     status_t setClientTarget(HalDisplayId, uint32_t slot, const sp<Fence>& acquireFence,
@@ -348,8 +347,8 @@
 
     // Present layers to the display and read releaseFences.
     status_t presentAndGetReleaseFences(
-            HalDisplayId, std::chrono::steady_clock::time_point earliestPresentTime,
-            const std::shared_ptr<FenceTime>& previousPresentFence) override;
+            HalDisplayId,
+            std::optional<std::chrono::steady_clock::time_point> earliestPresentTime) override;
 
     // set power mode
     status_t setPowerMode(PhysicalDisplayId, hal::PowerMode mode) override;
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index 23de4fa..e0f6c45 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -293,10 +293,6 @@
     }
 }
 
-void HidlComposer::resetCommands(Display) {
-    mWriter.reset();
-}
-
 Error HidlComposer::executeCommands(Display) {
     return execute();
 }
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
index d04652b..0521acf 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -174,10 +174,6 @@
 
     void registerCallback(HWC2::ComposerCallback& callback) override;
 
-    // Reset all pending commands in the command buffer. Useful if you want to
-    // skip a frame but have already queued some commands.
-    void resetCommands(Display) override;
-
     // Explicitly flush all pending commands in the command buffer.
     Error executeCommands(Display) override;
 
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
index f05223c..37b68c8 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -31,7 +31,7 @@
 #include <utils/Mutex.h>
 #include <utils/Trace.h>
 
-#include <android/hardware/power/1.3/IPower.h>
+#include <android/hardware/power/IPower.h>
 #include <android/hardware/power/IPowerHintSession.h>
 #include <android/hardware/power/WorkDuration.h>
 
@@ -49,12 +49,7 @@
 
 namespace impl {
 
-namespace V1_0 = android::hardware::power::V1_0;
-namespace V1_3 = android::hardware::power::V1_3;
-using V1_3::PowerHint;
-
 using android::hardware::power::Boost;
-using android::hardware::power::IPower;
 using android::hardware::power::IPowerHintSession;
 using android::hardware::power::Mode;
 using android::hardware::power::SessionHint;
@@ -80,7 +75,8 @@
 
 } // namespace
 
-PowerAdvisor::PowerAdvisor(SurfaceFlinger& flinger) : mFlinger(flinger) {
+PowerAdvisor::PowerAdvisor(SurfaceFlinger& flinger)
+      : mPowerHal(std::make_unique<power::PowerHalController>()), mFlinger(flinger) {
     if (getUpdateTimeout() > 0ms) {
         mScreenUpdateTimer.emplace("UpdateImminentTimer", getUpdateTimeout(),
                                    /* resetCallback */ nullptr,
@@ -117,6 +113,10 @@
 }
 
 void PowerAdvisor::setExpensiveRenderingExpected(DisplayId displayId, bool expected) {
+    if (!mHasExpensiveRendering) {
+        ALOGV("Skipped sending EXPENSIVE_RENDERING because HAL doesn't support it");
+        return;
+    }
     if (expected) {
         mExpensiveDisplays.insert(displayId);
     } else {
@@ -125,19 +125,16 @@
 
     const bool expectsExpensiveRendering = !mExpensiveDisplays.empty();
     if (mNotifiedExpensiveRendering != expectsExpensiveRendering) {
-        std::lock_guard lock(mPowerHalMutex);
-        HalWrapper* const halWrapper = getPowerHal();
-        if (halWrapper == nullptr) {
-            return;
-        }
-
-        if (!halWrapper->setExpensiveRendering(expectsExpensiveRendering)) {
-            // The HAL has become unavailable; attempt to reconnect later
-            mReconnectPowerHal = true;
+        auto ret = getPowerHal().setMode(Mode::EXPENSIVE_RENDERING, expectsExpensiveRendering);
+        if (!ret.isOk()) {
+            if (ret.isUnsupported()) {
+                mHasExpensiveRendering = false;
+            }
             return;
         }
 
         mNotifiedExpensiveRendering = expectsExpensiveRendering;
+        traceExpensiveRendering(mNotifiedExpensiveRendering);
     }
 }
 
@@ -149,16 +146,22 @@
     }
 
     if (mSendUpdateImminent.exchange(false)) {
-        std::lock_guard lock(mPowerHalMutex);
-        HalWrapper* const halWrapper = getPowerHal();
-        if (halWrapper == nullptr) {
-            return;
+        ALOGV("AIDL notifyDisplayUpdateImminentAndCpuReset");
+        if (usePowerHintSession() && ensurePowerHintSessionRunning()) {
+            std::lock_guard lock(mHintSessionMutex);
+            auto ret = mHintSession->sendHint(SessionHint::CPU_LOAD_RESET);
+            if (!ret.isOk()) {
+                mHintSessionRunning = false;
+            }
         }
 
-        if (!halWrapper->notifyDisplayUpdateImminentAndCpuReset()) {
-            // The HAL has become unavailable; attempt to reconnect later
-            mReconnectPowerHal = true;
-            return;
+        if (!mHasDisplayUpdateImminent) {
+            ALOGV("Skipped sending DISPLAY_UPDATE_IMMINENT because HAL doesn't support it");
+        } else {
+            auto ret = getPowerHal().setBoost(Boost::DISPLAY_UPDATE_IMMINENT, 0);
+            if (ret.isUnsupported()) {
+                mHasDisplayUpdateImminent = false;
+            }
         }
 
         if (mScreenUpdateTimer) {
@@ -178,88 +181,123 @@
 // checks both if it supports and if it's enabled
 bool PowerAdvisor::usePowerHintSession() {
     // uses cached value since the underlying support and flag are unlikely to change at runtime
-    return mPowerHintEnabled.value_or(false) && supportsPowerHintSession();
+    return mHintSessionEnabled.value_or(false) && supportsPowerHintSession();
 }
 
 bool PowerAdvisor::supportsPowerHintSession() {
     // cache to avoid needing lock every time
-    if (!mSupportsPowerHint.has_value()) {
-        std::lock_guard lock(mPowerHalMutex);
-        HalWrapper* const halWrapper = getPowerHal();
-        mSupportsPowerHint = halWrapper && halWrapper->supportsPowerHintSession();
+    if (!mSupportsHintSession.has_value()) {
+        mSupportsHintSession = getPowerHal().getHintSessionPreferredRate().isOk();
     }
-    return *mSupportsPowerHint;
+    return *mSupportsHintSession;
 }
 
-bool PowerAdvisor::isPowerHintSessionRunning() {
-    return mPowerHintSessionRunning;
+bool PowerAdvisor::ensurePowerHintSessionRunning() {
+    if (!mHintSessionRunning && !mHintSessionThreadIds.empty() && usePowerHintSession()) {
+        startPowerHintSession(mHintSessionThreadIds);
+    }
+    return mHintSessionRunning;
 }
 
-void PowerAdvisor::setTargetWorkDuration(Duration targetDuration) {
+void PowerAdvisor::updateTargetWorkDuration(Duration targetDuration) {
     if (!usePowerHintSession()) {
         ALOGV("Power hint session target duration cannot be set, skipping");
         return;
     }
+    ATRACE_CALL();
     {
-        std::lock_guard lock(mPowerHalMutex);
-        HalWrapper* const halWrapper = getPowerHal();
-        if (halWrapper != nullptr) {
-            halWrapper->setTargetWorkDuration(targetDuration);
+        mTargetDuration = targetDuration;
+        if (sTraceHintSessionData) ATRACE_INT64("Time target", targetDuration.ns());
+        if (ensurePowerHintSessionRunning() && (targetDuration != mLastTargetDurationSent)) {
+            ALOGV("Sending target time: %" PRId64 "ns", targetDuration.ns());
+            mLastTargetDurationSent = targetDuration;
+            std::lock_guard lock(mHintSessionMutex);
+            auto ret = mHintSession->updateTargetWorkDuration(targetDuration.ns());
+            if (!ret.isOk()) {
+                ALOGW("Failed to set power hint target work duration with error: %s",
+                      ret.exceptionMessage().c_str());
+                mHintSessionRunning = false;
+            }
         }
     }
 }
 
-void PowerAdvisor::sendActualWorkDuration() {
+void PowerAdvisor::reportActualWorkDuration() {
     if (!mBootFinished || !usePowerHintSession()) {
         ALOGV("Actual work duration power hint cannot be sent, skipping");
         return;
     }
-    const std::optional<Duration> actualDuration = estimateWorkDuration(false);
-    if (actualDuration.has_value()) {
-        std::lock_guard lock(mPowerHalMutex);
-        HalWrapper* const halWrapper = getPowerHal();
-        if (halWrapper != nullptr) {
-            halWrapper->sendActualWorkDuration(*actualDuration + kTargetSafetyMargin,
-                                               TimePoint::now());
-        }
-    }
-}
-
-void PowerAdvisor::sendPredictedWorkDuration() {
-    if (!mBootFinished || !usePowerHintSession()) {
-        ALOGV("Actual work duration power hint cannot be sent, skipping");
+    ATRACE_CALL();
+    std::optional<Duration> actualDuration = estimateWorkDuration();
+    if (!actualDuration.has_value() || actualDuration < 0ns || !ensurePowerHintSessionRunning()) {
+        ALOGV("Failed to send actual work duration, skipping");
         return;
     }
+    actualDuration = std::make_optional(*actualDuration + sTargetSafetyMargin);
+    mActualDuration = actualDuration;
+    WorkDuration duration;
+    duration.durationNanos = actualDuration->ns();
+    duration.timeStampNanos = TimePoint::now().ns();
+    mHintSessionQueue.push_back(duration);
 
-    const std::optional<Duration> predictedDuration = estimateWorkDuration(true);
+    if (sTraceHintSessionData) {
+        ATRACE_INT64("Measured duration", actualDuration->ns());
+        ATRACE_INT64("Target error term", Duration{*actualDuration - mTargetDuration}.ns());
+        ATRACE_INT64("Reported duration", actualDuration->ns());
+        ATRACE_INT64("Reported target", mLastTargetDurationSent.ns());
+        ATRACE_INT64("Reported target error term",
+                     Duration{*actualDuration - mLastTargetDurationSent}.ns());
+    }
 
-    if (predictedDuration.has_value()) {
-        std::lock_guard lock(mPowerHalMutex);
-        HalWrapper* const halWrapper = getPowerHal();
-        if (halWrapper != nullptr) {
-            halWrapper->sendActualWorkDuration(*predictedDuration + kTargetSafetyMargin,
-                                               TimePoint::now());
+    ALOGV("Sending actual work duration of: %" PRId64 " on reported target: %" PRId64
+          " with error: %" PRId64,
+          actualDuration->ns(), mLastTargetDurationSent.ns(),
+          Duration{*actualDuration - mLastTargetDurationSent}.ns());
+
+    {
+        std::lock_guard lock(mHintSessionMutex);
+        auto ret = mHintSession->reportActualWorkDuration(mHintSessionQueue);
+        if (!ret.isOk()) {
+            ALOGW("Failed to report actual work durations with error: %s",
+                  ret.exceptionMessage().c_str());
+            mHintSessionRunning = false;
+            return;
         }
     }
+    mHintSessionQueue.clear();
 }
 
-void PowerAdvisor::enablePowerHint(bool enabled) {
-    mPowerHintEnabled = enabled;
+void PowerAdvisor::enablePowerHintSession(bool enabled) {
+    mHintSessionEnabled = enabled;
 }
 
 bool PowerAdvisor::startPowerHintSession(const std::vector<int32_t>& threadIds) {
-    if (!usePowerHintSession()) {
-        ALOGI("Power hint session cannot be started, skipping");
+    if (!mBootFinished.load()) {
+        return false;
     }
+    if (!usePowerHintSession()) {
+        ALOGI("Cannot start power hint session: disabled or unsupported");
+        return false;
+    }
+    if (mHintSessionRunning) {
+        ALOGE("Cannot start power hint session: already running");
+        return false;
+    }
+    LOG_ALWAYS_FATAL_IF(threadIds.empty(), "No thread IDs provided to power hint session!");
     {
-        std::lock_guard lock(mPowerHalMutex);
-        HalWrapper* halWrapper = getPowerHal();
-        if (halWrapper != nullptr && usePowerHintSession()) {
-            halWrapper->setPowerHintSessionThreadIds(threadIds);
-            mPowerHintSessionRunning = halWrapper->startPowerHintSession();
+        std::lock_guard lock(mHintSessionMutex);
+        mHintSession = nullptr;
+        mHintSessionThreadIds = threadIds;
+
+        auto ret = getPowerHal().createHintSession(getpid(), static_cast<int32_t>(getuid()),
+                                                   threadIds, mTargetDuration.ns());
+
+        if (ret.isOk()) {
+            mHintSessionRunning = true;
+            mHintSession = ret.value();
         }
     }
-    return mPowerHintSessionRunning;
+    return mHintSessionRunning;
 }
 
 void PowerAdvisor::setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) {
@@ -357,13 +395,13 @@
     return sortedDisplays;
 }
 
-std::optional<Duration> PowerAdvisor::estimateWorkDuration(bool earlyHint) {
-    if (earlyHint && (!mExpectedPresentTimes.isFull() || !mCommitStartTimes.isFull())) {
+std::optional<Duration> PowerAdvisor::estimateWorkDuration() {
+    if (!mExpectedPresentTimes.isFull() || !mCommitStartTimes.isFull()) {
         return std::nullopt;
     }
 
     // Tracks when we finish presenting to hwc
-    TimePoint estimatedEndTime = mCommitStartTimes[0];
+    TimePoint estimatedHwcEndTime = mCommitStartTimes[0];
 
     // How long we spent this frame not doing anything, waiting for fences or vsync
     Duration idleDuration = 0ns;
@@ -376,21 +414,11 @@
     // used to accumulate gpu time as we iterate over the active displays
     std::optional<TimePoint> estimatedGpuEndTime;
 
-    // If we're predicting at the start of the frame, we use last frame as our reference point
-    // If we're predicting at the end of the frame, we use the current frame as a reference point
-    TimePoint referenceFrameStartTime = (earlyHint ? mCommitStartTimes[-1] : mCommitStartTimes[0]);
-
-    // When the prior frame should be presenting to the display
-    // If we're predicting at the start of the frame, we use last frame's expected present time
-    // If we're predicting at the end of the frame, the present fence time is already known
-    TimePoint lastFramePresentTime =
-            (earlyHint ? mExpectedPresentTimes[-1] : mLastPresentFenceTime);
-
     // The timing info for the previously calculated display, if there was one
-    std::optional<DisplayTimeline> previousDisplayReferenceTiming;
+    std::optional<DisplayTimeline> previousDisplayTiming;
     std::vector<DisplayId>&& displayIds =
             getOrderedDisplayIds(&DisplayTimingData::hwcPresentStartTime);
-    DisplayTimeline referenceTiming, estimatedTiming;
+    DisplayTimeline displayTiming;
 
     // Iterate over the displays that use hwc in the same order they are presented
     for (DisplayId displayId : displayIds) {
@@ -400,35 +428,26 @@
 
         auto& displayData = mDisplayTimingData.at(displayId);
 
-        // mLastPresentFenceTime should always be the time of the reference frame, since it will be
-        // the previous frame's present fence if called at the start, and current frame's if called
-        // at the end
-        referenceTiming = displayData.calculateDisplayTimeline(mLastPresentFenceTime);
+        displayTiming = displayData.calculateDisplayTimeline(mLastPresentFenceTime);
 
         // If this is the first display, include the duration before hwc present starts
-        if (!previousDisplayReferenceTiming.has_value()) {
-            estimatedEndTime += referenceTiming.hwcPresentStartTime - referenceFrameStartTime;
+        if (!previousDisplayTiming.has_value()) {
+            estimatedHwcEndTime += displayTiming.hwcPresentStartTime - mCommitStartTimes[0];
         } else { // Otherwise add the time since last display's hwc present finished
-            estimatedEndTime += referenceTiming.hwcPresentStartTime -
-                    previousDisplayReferenceTiming->hwcPresentEndTime;
+            estimatedHwcEndTime +=
+                    displayTiming.hwcPresentStartTime - previousDisplayTiming->hwcPresentEndTime;
         }
 
-        // Late hint can re-use reference timing here since it's estimating its own reference frame
-        estimatedTiming = earlyHint
-                ? referenceTiming.estimateTimelineFromReference(lastFramePresentTime,
-                                                                estimatedEndTime)
-                : referenceTiming;
-
         // Update predicted present finish time with this display's present time
-        estimatedEndTime = estimatedTiming.hwcPresentEndTime;
+        estimatedHwcEndTime = displayTiming.hwcPresentEndTime;
 
         // Track how long we spent waiting for the fence, can be excluded from the timing estimate
-        idleDuration += estimatedTiming.probablyWaitsForPresentFence
-                ? lastFramePresentTime - estimatedTiming.presentFenceWaitStartTime
+        idleDuration += displayTiming.probablyWaitsForPresentFence
+                ? mLastPresentFenceTime - displayTiming.presentFenceWaitStartTime
                 : 0ns;
 
         // Track how long we spent waiting to present, can be excluded from the timing estimate
-        idleDuration += earlyHint ? 0ns : referenceTiming.hwcPresentDelayDuration;
+        idleDuration += displayTiming.hwcPresentDelayDuration;
 
         // Estimate the reference frame's gpu timing
         auto gpuTiming = displayData.estimateGpuTiming(previousValidGpuEndTime);
@@ -436,24 +455,24 @@
             previousValidGpuEndTime = gpuTiming->startTime + gpuTiming->duration;
 
             // Estimate the prediction frame's gpu end time from the reference frame
-            estimatedGpuEndTime = std::max(estimatedTiming.hwcPresentStartTime,
+            estimatedGpuEndTime = std::max(displayTiming.hwcPresentStartTime,
                                            estimatedGpuEndTime.value_or(TimePoint{0ns})) +
                     gpuTiming->duration;
         }
-        previousDisplayReferenceTiming = referenceTiming;
+        previousDisplayTiming = displayTiming;
     }
     ATRACE_INT64("Idle duration", idleDuration.ns());
 
-    TimePoint estimatedFlingerEndTime = earlyHint ? estimatedEndTime : mLastSfPresentEndTime;
+    TimePoint estimatedFlingerEndTime = mLastSfPresentEndTime;
 
     // Don't count time spent idly waiting in the estimate as we could do more work in that time
-    estimatedEndTime -= idleDuration;
+    estimatedHwcEndTime -= idleDuration;
     estimatedFlingerEndTime -= idleDuration;
 
     // We finish the frame when both present and the gpu are done, so wait for the later of the two
     // Also add the frame delay duration since the target did not move while we were delayed
     Duration totalDuration = mFrameDelayDuration +
-            std::max(estimatedEndTime, estimatedGpuEndTime.value_or(TimePoint{0ns})) -
+            std::max(estimatedHwcEndTime, estimatedGpuEndTime.value_or(TimePoint{0ns})) -
             mCommitStartTimes[0];
 
     // We finish SurfaceFlinger when post-composition finishes, so add that in here
@@ -468,10 +487,7 @@
 
 Duration PowerAdvisor::combineTimingEstimates(Duration totalDuration, Duration flingerDuration) {
     Duration targetDuration{0ns};
-    {
-        std::lock_guard lock(mPowerHalMutex);
-        targetDuration = *getPowerHal()->getTargetWorkDuration();
-    }
+    targetDuration = mTargetDuration;
     if (!mTotalFrameTargetDuration.has_value()) return flingerDuration;
 
     // Normalize total to the flinger target (vsync period) since that's how often we actually send
@@ -481,26 +497,6 @@
     return std::max(flingerDuration, normalizedTotalDuration);
 }
 
-PowerAdvisor::DisplayTimeline PowerAdvisor::DisplayTimeline::estimateTimelineFromReference(
-        TimePoint fenceTime, TimePoint displayStartTime) {
-    DisplayTimeline estimated;
-    estimated.hwcPresentStartTime = displayStartTime;
-
-    // We don't predict waiting for vsync alignment yet
-    estimated.hwcPresentDelayDuration = 0ns;
-
-    // How long we expect to run before we start waiting for the fence
-    // For now just re-use last frame's post-present duration and assume it will not change much
-    // Excludes time spent waiting for vsync since that's not going to be consistent
-    estimated.presentFenceWaitStartTime = estimated.hwcPresentStartTime +
-            (presentFenceWaitStartTime - (hwcPresentStartTime + hwcPresentDelayDuration));
-    estimated.probablyWaitsForPresentFence = fenceTime > estimated.presentFenceWaitStartTime;
-    estimated.hwcPresentEndTime = postPresentFenceHwcPresentDuration +
-            (estimated.probablyWaitsForPresentFence ? fenceTime
-                                                    : estimated.presentFenceWaitStartTime);
-    return estimated;
-}
-
 PowerAdvisor::DisplayTimeline PowerAdvisor::DisplayTimingData::calculateDisplayTimeline(
         TimePoint fenceTime) {
     DisplayTimeline timeline;
@@ -561,317 +557,17 @@
     return GpuTimeline{.duration = gpuDuration, .startTime = latestGpuStartTime};
 }
 
-class HidlPowerHalWrapper : public PowerAdvisor::HalWrapper {
-public:
-    HidlPowerHalWrapper(sp<V1_3::IPower> powerHal) : mPowerHal(std::move(powerHal)) {}
-
-    ~HidlPowerHalWrapper() override = default;
-
-    static std::unique_ptr<HalWrapper> connect() {
-        // Power HAL 1.3 is not guaranteed to be available, thus we need to query
-        // Power HAL 1.0 first and try to cast it to Power HAL 1.3.
-        sp<V1_3::IPower> powerHal = nullptr;
-        sp<V1_0::IPower> powerHal_1_0 = V1_0::IPower::getService();
-        if (powerHal_1_0 != nullptr) {
-            // Try to cast to Power HAL 1.3
-            powerHal = V1_3::IPower::castFrom(powerHal_1_0);
-            if (powerHal == nullptr) {
-                ALOGW("No Power HAL 1.3 service in system, disabling PowerAdvisor");
-            } else {
-                ALOGI("Loaded Power HAL 1.3 service");
-            }
-        } else {
-            ALOGW("No Power HAL found, disabling PowerAdvisor");
-        }
-
-        if (powerHal == nullptr) {
-            return nullptr;
-        }
-
-        return std::make_unique<HidlPowerHalWrapper>(std::move(powerHal));
-    }
-
-    bool setExpensiveRendering(bool enabled) override {
-        ALOGV("HIDL setExpensiveRendering %s", enabled ? "T" : "F");
-        auto ret = mPowerHal->powerHintAsync_1_3(PowerHint::EXPENSIVE_RENDERING, enabled);
-        if (ret.isOk()) {
-            traceExpensiveRendering(enabled);
-        }
-        return ret.isOk();
-    }
-
-    bool notifyDisplayUpdateImminentAndCpuReset() override {
-        // Power HAL 1.x doesn't have a notification for this
-        ALOGV("HIDL notifyUpdateImminent received but can't send");
-        return true;
-    }
-
-    bool supportsPowerHintSession() override { return false; }
-
-    bool isPowerHintSessionRunning() override { return false; }
-
-    void restartPowerHintSession() override {}
-
-    void setPowerHintSessionThreadIds(const std::vector<int32_t>&) override {}
-
-    bool startPowerHintSession() override { return false; }
-
-    void setTargetWorkDuration(Duration) override {}
-
-    void sendActualWorkDuration(Duration, TimePoint) override {}
-
-    bool shouldReconnectHAL() override { return false; }
-
-    std::vector<int32_t> getPowerHintSessionThreadIds() override { return std::vector<int32_t>{}; }
-
-    std::optional<Duration> getTargetWorkDuration() override { return std::nullopt; }
-
-private:
-    const sp<V1_3::IPower> mPowerHal = nullptr;
-};
-
-AidlPowerHalWrapper::AidlPowerHalWrapper(sp<IPower> powerHal) : mPowerHal(std::move(powerHal)) {
-    auto ret = mPowerHal->isModeSupported(Mode::EXPENSIVE_RENDERING, &mHasExpensiveRendering);
-    if (!ret.isOk()) {
-        mHasExpensiveRendering = false;
-    }
-
-    ret = mPowerHal->isBoostSupported(Boost::DISPLAY_UPDATE_IMMINENT, &mHasDisplayUpdateImminent);
-    if (!ret.isOk()) {
-        mHasDisplayUpdateImminent = false;
-    }
-
-    mSupportsPowerHint = checkPowerHintSessionSupported();
-}
-
-AidlPowerHalWrapper::~AidlPowerHalWrapper() {
-    if (mPowerHintSession != nullptr) {
-        mPowerHintSession->close();
-        mPowerHintSession = nullptr;
-    }
-}
-
-std::unique_ptr<PowerAdvisor::HalWrapper> AidlPowerHalWrapper::connect() {
-    // This only waits if the service is actually declared
-    sp<IPower> powerHal = waitForVintfService<IPower>();
-    if (powerHal == nullptr) {
-        return nullptr;
-    }
-    ALOGI("Loaded AIDL Power HAL service");
-
-    return std::make_unique<AidlPowerHalWrapper>(std::move(powerHal));
-}
-
-bool AidlPowerHalWrapper::setExpensiveRendering(bool enabled) {
-    ALOGV("AIDL setExpensiveRendering %s", enabled ? "T" : "F");
-    if (!mHasExpensiveRendering) {
-        ALOGV("Skipped sending EXPENSIVE_RENDERING because HAL doesn't support it");
-        return true;
-    }
-
-    auto ret = mPowerHal->setMode(Mode::EXPENSIVE_RENDERING, enabled);
-    if (ret.isOk()) {
-        traceExpensiveRendering(enabled);
-    }
-    return ret.isOk();
-}
-
-bool AidlPowerHalWrapper::notifyDisplayUpdateImminentAndCpuReset() {
-    ALOGV("AIDL notifyDisplayUpdateImminentAndCpuReset");
-    if (isPowerHintSessionRunning()) {
-        mPowerHintSession->sendHint(SessionHint::CPU_LOAD_RESET);
-    }
-
-    if (!mHasDisplayUpdateImminent) {
-        ALOGV("Skipped sending DISPLAY_UPDATE_IMMINENT because HAL doesn't support it");
-        return true;
-    }
-
-    auto ret = mPowerHal->setBoost(Boost::DISPLAY_UPDATE_IMMINENT, 0);
-    return ret.isOk();
-}
-
-// Only version 2+ of the aidl supports power hint sessions, hidl has no support
-bool AidlPowerHalWrapper::supportsPowerHintSession() {
-    return mSupportsPowerHint;
-}
-
-bool AidlPowerHalWrapper::checkPowerHintSessionSupported() {
-    int64_t unused;
-    // Try to get preferred rate to determine if hint sessions are supported
-    // We check for isOk not EX_UNSUPPORTED_OPERATION to lump together errors
-    return mPowerHal->getHintSessionPreferredRate(&unused).isOk();
-}
-
-bool AidlPowerHalWrapper::isPowerHintSessionRunning() {
-    return mPowerHintSession != nullptr;
-}
-
-void AidlPowerHalWrapper::closePowerHintSession() {
-    if (mPowerHintSession != nullptr) {
-        mPowerHintSession->close();
-        mPowerHintSession = nullptr;
-    }
-}
-
-void AidlPowerHalWrapper::restartPowerHintSession() {
-    closePowerHintSession();
-    startPowerHintSession();
-}
-
-void AidlPowerHalWrapper::setPowerHintSessionThreadIds(const std::vector<int32_t>& threadIds) {
-    if (threadIds != mPowerHintThreadIds) {
-        mPowerHintThreadIds = threadIds;
-        if (isPowerHintSessionRunning()) {
-            restartPowerHintSession();
-        }
-    }
-}
-
-bool AidlPowerHalWrapper::startPowerHintSession() {
-    if (mPowerHintSession != nullptr || mPowerHintThreadIds.empty()) {
-        ALOGV("Cannot start power hint session, skipping");
-        return false;
-    }
-    auto ret = mPowerHal->createHintSession(getpid(), static_cast<int32_t>(getuid()),
-                                            mPowerHintThreadIds, mTargetDuration.ns(),
-                                            &mPowerHintSession);
-    if (!ret.isOk()) {
-        ALOGW("Failed to start power hint session with error: %s",
-              ret.exceptionToString(ret.exceptionCode()).c_str());
-    } else {
-        mLastTargetDurationSent = mTargetDuration;
-    }
-    return isPowerHintSessionRunning();
-}
-
-void AidlPowerHalWrapper::setTargetWorkDuration(Duration targetDuration) {
-    ATRACE_CALL();
-    mTargetDuration = targetDuration;
-    if (sTraceHintSessionData) ATRACE_INT64("Time target", targetDuration.ns());
-    if (isPowerHintSessionRunning() && (targetDuration != mLastTargetDurationSent)) {
-        ALOGV("Sending target time: %" PRId64 "ns", targetDuration.ns());
-        mLastTargetDurationSent = targetDuration;
-        auto ret = mPowerHintSession->updateTargetWorkDuration(targetDuration.ns());
-        if (!ret.isOk()) {
-            ALOGW("Failed to set power hint target work duration with error: %s",
-                  ret.exceptionMessage().c_str());
-            mShouldReconnectHal = true;
-        }
-    }
-}
-
-void AidlPowerHalWrapper::sendActualWorkDuration(Duration actualDuration, TimePoint timestamp) {
-    ATRACE_CALL();
-    if (actualDuration < 0ns || !isPowerHintSessionRunning()) {
-        ALOGV("Failed to send actual work duration, skipping");
-        return;
-    }
-    mActualDuration = actualDuration;
-    WorkDuration duration;
-    duration.durationNanos = actualDuration.ns();
-    duration.timeStampNanos = timestamp.ns();
-    mPowerHintQueue.push_back(duration);
-
-    if (sTraceHintSessionData) {
-        ATRACE_INT64("Measured duration", actualDuration.ns());
-        ATRACE_INT64("Target error term", Duration{actualDuration - mTargetDuration}.ns());
-
-        ATRACE_INT64("Reported duration", actualDuration.ns());
-        ATRACE_INT64("Reported target", mLastTargetDurationSent.ns());
-        ATRACE_INT64("Reported target error term",
-                     Duration{actualDuration - mLastTargetDurationSent}.ns());
-    }
-
-    ALOGV("Sending actual work duration of: %" PRId64 " on reported target: %" PRId64
-          " with error: %" PRId64,
-          actualDuration.ns(), mLastTargetDurationSent.ns(),
-          Duration{actualDuration - mLastTargetDurationSent}.ns());
-
-    auto ret = mPowerHintSession->reportActualWorkDuration(mPowerHintQueue);
-    if (!ret.isOk()) {
-        ALOGW("Failed to report actual work durations with error: %s",
-              ret.exceptionMessage().c_str());
-        mShouldReconnectHal = true;
-    }
-    mPowerHintQueue.clear();
-}
-
-bool AidlPowerHalWrapper::shouldReconnectHAL() {
-    return mShouldReconnectHal;
-}
-
-std::vector<int32_t> AidlPowerHalWrapper::getPowerHintSessionThreadIds() {
-    return mPowerHintThreadIds;
-}
-
-std::optional<Duration> AidlPowerHalWrapper::getTargetWorkDuration() {
-    return mTargetDuration;
-}
-
-const bool AidlPowerHalWrapper::sTraceHintSessionData =
+const bool PowerAdvisor::sTraceHintSessionData =
         base::GetBoolProperty(std::string("debug.sf.trace_hint_sessions"), false);
 
-PowerAdvisor::HalWrapper* PowerAdvisor::getPowerHal() {
-    if (!mHasHal) {
-        return nullptr;
-    }
+const Duration PowerAdvisor::sTargetSafetyMargin = std::chrono::microseconds(
+        base::GetIntProperty<int64_t>("debug.sf.hint_margin_us",
+                                      ticks<std::micro>(PowerAdvisor::kDefaultTargetSafetyMargin)));
 
-    // Grab old hint session values before we destroy any existing wrapper
-    std::vector<int32_t> oldPowerHintSessionThreadIds;
-    std::optional<Duration> oldTargetWorkDuration;
-
-    if (mHalWrapper != nullptr) {
-        oldPowerHintSessionThreadIds = mHalWrapper->getPowerHintSessionThreadIds();
-        oldTargetWorkDuration = mHalWrapper->getTargetWorkDuration();
-    }
-
-    // If we used to have a HAL, but it stopped responding, attempt to reconnect
-    if (mReconnectPowerHal) {
-        mHalWrapper = nullptr;
-        mReconnectPowerHal = false;
-    }
-
-    if (mHalWrapper != nullptr) {
-        auto wrapper = mHalWrapper.get();
-        // If the wrapper is fine, return it, but if it indicates a reconnect, remake it
-        if (!wrapper->shouldReconnectHAL()) {
-            return wrapper;
-        }
-        ALOGD("Reconnecting Power HAL");
-        mHalWrapper = nullptr;
-    }
-
-    // At this point, we know for sure there is no running session
-    mPowerHintSessionRunning = false;
-
-    // First attempt to connect to the AIDL Power HAL
-    mHalWrapper = AidlPowerHalWrapper::connect();
-
-    // If that didn't succeed, attempt to connect to the HIDL Power HAL
-    if (mHalWrapper == nullptr) {
-        mHalWrapper = HidlPowerHalWrapper::connect();
-    } else {
-        ALOGD("Successfully connecting AIDL Power HAL");
-        // If AIDL, pass on any existing hint session values
-        mHalWrapper->setPowerHintSessionThreadIds(oldPowerHintSessionThreadIds);
-        // Only set duration and start if duration is defined
-        if (oldTargetWorkDuration.has_value()) {
-            mHalWrapper->setTargetWorkDuration(*oldTargetWorkDuration);
-            // Only start if possible to run and both threadids and duration are defined
-            if (usePowerHintSession() && !oldPowerHintSessionThreadIds.empty()) {
-                mPowerHintSessionRunning = mHalWrapper->startPowerHintSession();
-            }
-        }
-    }
-
-    // If we make it to this point and still don't have a HAL, it's unlikely we
-    // will, so stop trying
-    if (mHalWrapper == nullptr) {
-        mHasHal = false;
-    }
-
-    return mHalWrapper.get();
+power::PowerHalController& PowerAdvisor::getPowerHal() {
+    static std::once_flag halFlag;
+    std::call_once(halFlag, [this] { mPowerHal->init(); });
+    return *mPowerHal;
 }
 
 } // namespace impl
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
index d45e7cb..7a0d426 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
@@ -27,6 +27,7 @@
 
 #include <android/hardware/power/IPower.h>
 #include <compositionengine/impl/OutputCompositionState.h>
+#include <powermanager/PowerHalController.h>
 #include <scheduler/Time.h>
 #include <ui/DisplayIdentification.h>
 #include "../Scheduler/OneShotTimer.h"
@@ -52,15 +53,14 @@
     // Checks both if it supports and if it's enabled
     virtual bool usePowerHintSession() = 0;
     virtual bool supportsPowerHintSession() = 0;
-    virtual bool isPowerHintSessionRunning() = 0;
+
+    virtual bool ensurePowerHintSessionRunning() = 0;
     // Sends a power hint that updates to the target work duration for the frame
-    virtual void setTargetWorkDuration(Duration targetDuration) = 0;
+    virtual void updateTargetWorkDuration(Duration targetDuration) = 0;
     // Sends a power hint for the actual known work duration at the end of the frame
-    virtual void sendActualWorkDuration() = 0;
-    // Sends a power hint for the upcoming frame predicted from previous frame timing
-    virtual void sendPredictedWorkDuration() = 0;
+    virtual void reportActualWorkDuration() = 0;
     // Sets whether the power hint session is enabled
-    virtual void enablePowerHint(bool enabled) = 0;
+    virtual void enablePowerHintSession(bool enabled) = 0;
     // Initializes the power hint session
     virtual bool startPowerHintSession(const std::vector<int32_t>& threadIds) = 0;
     // Provides PowerAdvisor with a copy of the gpu fence so it can determine the gpu end time
@@ -101,24 +101,6 @@
 // full state of the system when sending out power hints to things like the GPU.
 class PowerAdvisor final : public Hwc2::PowerAdvisor {
 public:
-    class HalWrapper {
-    public:
-        virtual ~HalWrapper() = default;
-
-        virtual bool setExpensiveRendering(bool enabled) = 0;
-        virtual bool notifyDisplayUpdateImminentAndCpuReset() = 0;
-        virtual bool supportsPowerHintSession() = 0;
-        virtual bool isPowerHintSessionRunning() = 0;
-        virtual void restartPowerHintSession() = 0;
-        virtual void setPowerHintSessionThreadIds(const std::vector<int32_t>& threadIds) = 0;
-        virtual bool startPowerHintSession() = 0;
-        virtual void setTargetWorkDuration(Duration targetDuration) = 0;
-        virtual void sendActualWorkDuration(Duration actualDuration, TimePoint timestamp) = 0;
-        virtual bool shouldReconnectHAL() = 0;
-        virtual std::vector<int32_t> getPowerHintSessionThreadIds() = 0;
-        virtual std::optional<Duration> getTargetWorkDuration() = 0;
-    };
-
     PowerAdvisor(SurfaceFlinger& flinger);
     ~PowerAdvisor() override;
 
@@ -129,11 +111,10 @@
     void notifyDisplayUpdateImminentAndCpuReset() override;
     bool usePowerHintSession() override;
     bool supportsPowerHintSession() override;
-    bool isPowerHintSessionRunning() override;
-    void setTargetWorkDuration(Duration targetDuration) override;
-    void sendActualWorkDuration() override;
-    void sendPredictedWorkDuration() override;
-    void enablePowerHint(bool enabled) override;
+    bool ensurePowerHintSessionRunning() override;
+    void updateTargetWorkDuration(Duration targetDuration) override;
+    void reportActualWorkDuration() override;
+    void enablePowerHintSession(bool enabled) override;
     bool startPowerHintSession(const std::vector<int32_t>& threadIds) override;
     void setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime);
     void setHwcValidateTiming(DisplayId displayId, TimePoint validateStartTime,
@@ -155,15 +136,7 @@
 private:
     friend class PowerAdvisorTest;
 
-    // Tracks if powerhal exists
-    bool mHasHal = true;
-    // Holds the hal wrapper for getPowerHal
-    std::unique_ptr<HalWrapper> mHalWrapper GUARDED_BY(mPowerHalMutex) = nullptr;
-
-    HalWrapper* getPowerHal() REQUIRES(mPowerHalMutex);
-    bool mReconnectPowerHal GUARDED_BY(mPowerHalMutex) = false;
-    std::mutex mPowerHalMutex;
-
+    std::unique_ptr<power::PowerHalController> mPowerHal;
     std::atomic_bool mBootFinished = false;
 
     std::unordered_set<DisplayId> mExpensiveDisplays;
@@ -189,9 +162,6 @@
         Duration postPresentFenceHwcPresentDuration{0ns};
         // Are we likely to have waited for the present fence during composition
         bool probablyWaitsForPresentFence = false;
-        // Estimate one frame's timeline from that of a previous frame
-        DisplayTimeline estimateTimelineFromReference(TimePoint fenceTime,
-                                                      TimePoint displayStartTime);
     };
 
     struct GpuTimeline {
@@ -243,8 +213,7 @@
     std::vector<DisplayId> getOrderedDisplayIds(
             std::optional<TimePoint> DisplayTimingData::*sortBy);
     // Estimates a frame's total work duration including gpu time.
-    // Runs either at the beginning or end of a frame, using the most recent data available
-    std::optional<Duration> estimateWorkDuration(bool earlyHint);
+    std::optional<Duration> estimateWorkDuration();
     // There are two different targets and actual work durations we care about,
     // this normalizes them together and takes the max of the two
     Duration combineTimingEstimates(Duration totalDuration, Duration flingerDuration);
@@ -268,67 +237,41 @@
     // Updated list of display IDs
     std::vector<DisplayId> mDisplayIds;
 
-    std::optional<bool> mPowerHintEnabled;
-    std::optional<bool> mSupportsPowerHint;
-    bool mPowerHintSessionRunning = false;
+    // Ensure powerhal connection is initialized
+    power::PowerHalController& getPowerHal();
 
-    // An adjustable safety margin which pads the "actual" value sent to PowerHAL,
-    // encouraging more aggressive boosting to give SurfaceFlinger a larger margin for error
-    static constexpr const Duration kTargetSafetyMargin{1ms};
+    std::optional<bool> mHintSessionEnabled;
+    std::optional<bool> mSupportsHintSession;
+    bool mHintSessionRunning = false;
 
-    // How long we expect hwc to run after the present call until it waits for the fence
-    static constexpr const Duration kFenceWaitStartDelayValidated{150us};
-    static constexpr const Duration kFenceWaitStartDelaySkippedValidate{250us};
-};
+    std::mutex mHintSessionMutex;
+    sp<hardware::power::IPowerHintSession> mHintSession GUARDED_BY(mHintSessionMutex) = nullptr;
 
-class AidlPowerHalWrapper : public PowerAdvisor::HalWrapper {
-public:
-    explicit AidlPowerHalWrapper(sp<hardware::power::IPower> powerHal);
-    ~AidlPowerHalWrapper() override;
-
-    static std::unique_ptr<HalWrapper> connect();
-
-    bool setExpensiveRendering(bool enabled) override;
-    bool notifyDisplayUpdateImminentAndCpuReset() override;
-    bool supportsPowerHintSession() override;
-    bool isPowerHintSessionRunning() override;
-    void restartPowerHintSession() override;
-    void setPowerHintSessionThreadIds(const std::vector<int32_t>& threadIds) override;
-    bool startPowerHintSession() override;
-    void setTargetWorkDuration(Duration targetDuration) override;
-    void sendActualWorkDuration(Duration actualDuration, TimePoint timestamp) override;
-    bool shouldReconnectHAL() override;
-    std::vector<int32_t> getPowerHintSessionThreadIds() override;
-    std::optional<Duration> getTargetWorkDuration() override;
-
-private:
-    friend class AidlPowerHalWrapperTest;
-
-    bool checkPowerHintSessionSupported();
-    void closePowerHintSession();
-
-    const sp<hardware::power::IPower> mPowerHal = nullptr;
-    bool mHasExpensiveRendering = false;
-    bool mHasDisplayUpdateImminent = false;
-    // Used to indicate an error state and need for reconstruction
-    bool mShouldReconnectHal = false;
-
-    // Power hint session data
-
-    // Concurrent access for this is protected by mPowerHalMutex
-    sp<hardware::power::IPowerHintSession> mPowerHintSession = nullptr;
+    // Initialize to true so we try to call, to check if it's supported
+    bool mHasExpensiveRendering = true;
+    bool mHasDisplayUpdateImminent = true;
     // Queue of actual durations saved to report
-    std::vector<hardware::power::WorkDuration> mPowerHintQueue;
+    std::vector<hardware::power::WorkDuration> mHintSessionQueue;
     // The latest values we have received for target and actual
     Duration mTargetDuration = kDefaultTargetDuration;
     std::optional<Duration> mActualDuration;
     // The list of thread ids, stored so we can restart the session from this class if needed
-    std::vector<int32_t> mPowerHintThreadIds;
-    bool mSupportsPowerHint = false;
+    std::vector<int32_t> mHintSessionThreadIds;
     Duration mLastTargetDurationSent = kDefaultTargetDuration;
     // Whether we should emit ATRACE_INT data for hint sessions
     static const bool sTraceHintSessionData;
-    static constexpr Duration kDefaultTargetDuration{16ms};
+
+    // Default target duration for the hint session
+    static constexpr const Duration kDefaultTargetDuration{16ms};
+
+    // An adjustable safety margin which pads the "actual" value sent to PowerHAL,
+    // encouraging more aggressive boosting to give SurfaceFlinger a larger margin for error
+    static const Duration sTargetSafetyMargin;
+    static constexpr const Duration kDefaultTargetSafetyMargin{1ms};
+
+    // How long we expect hwc to run after the present call until it waits for the fence
+    static constexpr const Duration kFenceWaitStartDelayValidated{150us};
+    static constexpr const Duration kFenceWaitStartDelaySkippedValidate{250us};
 };
 
 } // namespace impl
diff --git a/services/surfaceflinger/DisplayRenderArea.cpp b/services/surfaceflinger/DisplayRenderArea.cpp
index 8f39e26..e55cd3e 100644
--- a/services/surfaceflinger/DisplayRenderArea.cpp
+++ b/services/surfaceflinger/DisplayRenderArea.cpp
@@ -35,21 +35,24 @@
                                                       const Rect& sourceCrop, ui::Size reqSize,
                                                       ui::Dataspace reqDataSpace,
                                                       bool useIdentityTransform,
+                                                      bool hintForSeamlessTransition,
                                                       bool allowSecureLayers) {
     if (auto display = displayWeak.promote()) {
         // Using new to access a private constructor.
         return std::unique_ptr<DisplayRenderArea>(
                 new DisplayRenderArea(std::move(display), sourceCrop, reqSize, reqDataSpace,
-                                      useIdentityTransform, allowSecureLayers));
+                                      useIdentityTransform, hintForSeamlessTransition,
+                                      allowSecureLayers));
     }
     return nullptr;
 }
 
 DisplayRenderArea::DisplayRenderArea(sp<const DisplayDevice> display, const Rect& sourceCrop,
                                      ui::Size reqSize, ui::Dataspace reqDataSpace,
-                                     bool useIdentityTransform, bool allowSecureLayers)
-      : RenderArea(reqSize, CaptureFill::OPAQUE, reqDataSpace, allowSecureLayers,
-                   applyDeviceOrientation(useIdentityTransform, *display)),
+                                     bool useIdentityTransform, bool hintForSeamlessTransition,
+                                     bool allowSecureLayers)
+      : RenderArea(reqSize, CaptureFill::OPAQUE, reqDataSpace, hintForSeamlessTransition,
+                   allowSecureLayers, applyDeviceOrientation(useIdentityTransform, *display)),
         mDisplay(std::move(display)),
         mSourceCrop(sourceCrop) {}
 
diff --git a/services/surfaceflinger/DisplayRenderArea.h b/services/surfaceflinger/DisplayRenderArea.h
index ce5410a..9a4981c 100644
--- a/services/surfaceflinger/DisplayRenderArea.h
+++ b/services/surfaceflinger/DisplayRenderArea.h
@@ -30,6 +30,7 @@
     static std::unique_ptr<RenderArea> create(wp<const DisplayDevice>, const Rect& sourceCrop,
                                               ui::Size reqSize, ui::Dataspace,
                                               bool useIdentityTransform,
+                                              bool hintForSeamlessTransition,
                                               bool allowSecureLayers = true);
 
     const ui::Transform& getTransform() const override;
@@ -39,7 +40,8 @@
 
 private:
     DisplayRenderArea(sp<const DisplayDevice>, const Rect& sourceCrop, ui::Size reqSize,
-                      ui::Dataspace, bool useIdentityTransform, bool allowSecureLayers = true);
+                      ui::Dataspace, bool useIdentityTransform, bool hintForSeamlessTransition,
+                      bool allowSecureLayers = true);
 
     const sp<const DisplayDevice> mDisplay;
     const Rect mSourceCrop;
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index 925f111..ded734e 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -885,15 +885,17 @@
 
 void FrameTimeline::DisplayFrame::classifyJank(nsecs_t& deadlineDelta, nsecs_t& deltaToVsync,
                                                nsecs_t previousPresentTime) {
-    if (mPredictionState == PredictionState::Expired ||
-        mSurfaceFlingerActuals.presentTime == Fence::SIGNAL_TIME_INVALID) {
+    const bool presentTimeValid =
+            mSurfaceFlingerActuals.presentTime >= mSurfaceFlingerActuals.startTime;
+    if (mPredictionState == PredictionState::Expired || !presentTimeValid) {
         // Cannot do jank classification with expired predictions or invalid signal times. Set the
         // deltas to 0 as both negative and positive deltas are used as real values.
         mJankType = JankType::Unknown;
         deadlineDelta = 0;
         deltaToVsync = 0;
-        if (mSurfaceFlingerActuals.presentTime == Fence::SIGNAL_TIME_INVALID) {
+        if (!presentTimeValid) {
             mSurfaceFlingerActuals.presentTime = mSurfaceFlingerActuals.endTime;
+            mJankType |= JankType::DisplayHAL;
         }
 
         return;
diff --git a/services/surfaceflinger/FrontEnd/DisplayInfo.h b/services/surfaceflinger/FrontEnd/DisplayInfo.h
index 6b9d7a2..218a64a 100644
--- a/services/surfaceflinger/FrontEnd/DisplayInfo.h
+++ b/services/surfaceflinger/FrontEnd/DisplayInfo.h
@@ -28,8 +28,9 @@
     ui::Transform transform;
     bool receivesInput;
     bool isSecure;
-    // TODO(b/238781169) can eliminate once sPrimaryDisplayRotationFlags is removed.
+    // TODO(b/259407931): can eliminate once SurfaceFlinger::sActiveDisplayRotationFlags 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..97af445 100644
--- a/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp
@@ -23,6 +23,7 @@
 namespace android::surfaceflinger {
 
 std::atomic<uint32_t> LayerCreationArgs::sSequence{1};
+std::atomic<uint32_t> LayerCreationArgs::sInternalSequence{1};
 
 uint32_t LayerCreationArgs::getInternalLayerId(uint32_t id) {
     return id | INTERNAL_LAYER_PREFIX;
@@ -48,13 +49,11 @@
                 metadata.getInt32(gui::METADATA_OWNER_UID, static_cast<int32_t>(ownerUid)));
     }
 
-    if (id) {
+    if (internalLayer) {
+        sequence = id.value_or(getInternalLayerId(sInternalSequence++));
+    } else if (id) {
         sequence = *id;
-        if (internalLayer) {
-            sequence = getInternalLayerId(*id);
-        } else {
-            sSequence = *id + 1;
-        }
+        sSequence = *id + 1;
     } else {
         sequence = sSequence++;
         if (sequence >= INTERNAL_LAYER_PREFIX) {
@@ -65,7 +64,32 @@
     }
 }
 
-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);
+}
+
+std::string LayerCreationArgs::getDebugString() const {
+    std::stringstream stream;
+    stream << "LayerCreationArgs{" << name << "[" << sequence << "] flags=" << flags
+           << " pid=" << ownerPid << " uid=" << ownerUid;
+    if (addToRoot) {
+        stream << " addToRoot=" << addToRoot;
+    }
+    if (parentId != UNASSIGNED_LAYER_ID) {
+        stream << " parentId=" << parentId;
+    }
+    if (layerIdToMirror != UNASSIGNED_LAYER_ID) {
+        stream << " layerIdToMirror=" << layerIdToMirror;
+    }
+    if (layerStackToMirror != ui::INVALID_LAYER_STACK) {
+        stream << " layerStackToMirror=" << layerStackToMirror.id;
+    }
+    return stream.str();
+}
 
 } // namespace android::surfaceflinger
diff --git a/services/surfaceflinger/FrontEnd/LayerCreationArgs.h b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h
index 2cd6b55..c26edb5 100644
--- a/services/surfaceflinger/FrontEnd/LayerCreationArgs.h
+++ b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h
@@ -36,12 +36,16 @@
 
 struct LayerCreationArgs {
     static std::atomic<uint32_t> sSequence;
+    static std::atomic<uint32_t> sInternalSequence;
     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
+    std::string getDebugString() const;
 
     android::SurfaceFlinger* flinger;
     sp<android::Client> client;
@@ -56,6 +60,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/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
index c30465f..5913d4b 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
@@ -30,7 +30,7 @@
     auto lhsLayer = lhs.first->getLayer();
     auto rhsLayer = rhs.first->getLayer();
     if (lhsLayer->layerStack.id != rhsLayer->layerStack.id) {
-        return lhsLayer->layerStack.id > rhsLayer->layerStack.id;
+        return lhsLayer->layerStack.id < rhsLayer->layerStack.id;
     }
     if (lhsLayer->z != rhsLayer->z) {
         return lhsLayer->z < rhsLayer->z;
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
index 3dd89ba..b25b731 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.h
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
@@ -104,6 +104,16 @@
         static const TraversalPath ROOT;
     };
 
+    struct TraversalPathHash {
+        std::size_t operator()(const LayerHierarchy::TraversalPath& key) const {
+            uint32_t hashCode = key.id * 31;
+            if (key.mirrorRootId != UNASSIGNED_LAYER_ID) {
+                hashCode += key.mirrorRootId * 31;
+            }
+            return std::hash<size_t>{}(hashCode);
+        }
+    };
+
     // Helper class to add nodes to an existing traversal id and removes the
     // node when it goes out of scope.
     class ScopedAddToTraversalPath {
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
index 33cc429..cd9515c 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"
 
@@ -39,10 +38,11 @@
         RequestedLayerState& layer = *newLayer.get();
         auto [it, inserted] = mIdToLayer.try_emplace(layer.id, References{.owner = layer});
         if (!inserted) {
-            LOG_ALWAYS_FATAL("Duplicate layer id %d found. Existing layer: %s", layer.id,
+            LOG_ALWAYS_FATAL("Duplicate layer id found. New layer: %s Existing layer: %s",
+                             layer.getDebugString().c_str(),
                              it->second.owner.getDebugString().c_str());
         }
-
+        mAddedLayers.push_back(newLayer.get());
         layer.parentId = linkLayer(layer.parentId, layer.id);
         layer.relativeParentId = linkLayer(layer.relativeParentId, layer.id);
         if (layer.layerStackToMirror != ui::INVALID_LAYER_STACK) {
@@ -72,12 +72,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;
@@ -163,11 +165,12 @@
     }
 }
 
-void LayerLifecycleManager::applyTransactions(const std::vector<TransactionState>& transactions) {
+void LayerLifecycleManager::applyTransactions(const std::vector<TransactionState>& transactions,
+                                              bool ignoreUnknownLayers) {
     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,15 +178,15 @@
 
             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_IF(!ignoreUnknownLayers, "%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;
             }
 
@@ -198,13 +201,13 @@
 
             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(LayerCreationArgs::getInternalLayerId(
+                                                        LayerCreationArgs::sInternalSequence++),
+                                                /*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));
@@ -258,23 +261,19 @@
 }
 
 void LayerLifecycleManager::commitChanges() {
-    for (auto& layer : mLayers) {
-        if (layer->changes.test(RequestedLayerState::Changes::Created)) {
-            for (auto listener : mListeners) {
-                listener->onLayerAdded(*layer);
-            }
+    for (auto layer : mAddedLayers) {
+        for (auto& listener : mListeners) {
+            listener->onLayerAdded(*layer);
         }
+    }
+    mAddedLayers.clear();
+
+    for (auto& layer : mLayers) {
         layer->clearChanges();
     }
 
     for (auto& destroyedLayer : mDestroyedLayers) {
-        if (destroyedLayer->changes.test(RequestedLayerState::Changes::Created)) {
-            for (auto listener : mListeners) {
-                listener->onLayerAdded(*destroyedLayer);
-            }
-        }
-
-        for (auto listener : mListeners) {
+        for (auto& listener : mListeners) {
             listener->onLayerDestroyed(*destroyedLayer);
         }
     }
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
index 25d27ee..f0d2c22 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
@@ -39,8 +39,15 @@
 public:
     // External state changes should be updated in the following order:
     void addLayers(std::vector<std::unique_ptr<RequestedLayerState>>);
-    void applyTransactions(const std::vector<TransactionState>&);
-    void onHandlesDestroyed(const std::vector<uint32_t>&);
+    // Ignore unknown layers when interoping with legacy front end. In legacy we destroy
+    // the layers it is unreachable. When using the LayerLifecycleManager for layer trace
+    // generation we may encounter layers which are known because we don't have an explicit
+    // lifecycle. Ignore these errors while we have to interop with legacy.
+    void applyTransactions(const std::vector<TransactionState>&, bool ignoreUnknownLayers = false);
+    // 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
@@ -101,6 +108,9 @@
     std::vector<std::unique_ptr<RequestedLayerState>> mLayers;
     // Layers pending destruction. Layers will be destroyed once changes are committed.
     std::vector<std::unique_ptr<RequestedLayerState>> mDestroyedLayers;
+    // Keeps track of all the layers that were added in order. Changes will be cleared once
+    // committed.
+    std::vector<RequestedLayerState*> mAddedLayers;
 };
 
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
index 8a45093..a992584 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
@@ -27,10 +27,19 @@
 LayerSnapshot::LayerSnapshot(const RequestedLayerState& state,
                              const LayerHierarchy::TraversalPath& path)
       : path(path) {
-    static uint32_t sUniqueSequenceId = 0;
-    // Provide a unique id for clones otherwise keeping using the sequence id.
-    // The seq id can still be useful for debugging if its available.
-    uniqueSequence = (path.isClone()) ? sUniqueSequenceId++ : state.id;
+    // Provide a unique id for all snapshots.
+    // A front end layer can generate multiple snapshots if its mirrored.
+    // Additionally, if the layer is not reachable, we may choose to destroy
+    // and recreate the snapshot in which case the unique sequence id will
+    // change. The consumer shouldn't tie any lifetimes to this unique id but
+    // register a LayerLifecycleManager::ILifecycleListener or get a list of
+    // destroyed layers from LayerLifecycleManager.
+    if (path.isClone()) {
+        uniqueSequence =
+                LayerCreationArgs::getInternalLayerId(LayerCreationArgs::sInternalSequence++);
+    } else {
+        uniqueSequence = state.id;
+    }
     sequence = static_cast<int32_t>(state.id);
     name = state.name;
     textureName = state.textureName;
@@ -39,6 +48,8 @@
     inputInfo.id = static_cast<int32_t>(uniqueSequence);
     inputInfo.ownerUid = static_cast<int32_t>(state.ownerUid);
     inputInfo.ownerPid = state.ownerPid;
+    uid = state.ownerUid;
+    pid = state.ownerPid;
     changes = RequestedLayerState::Changes::Created;
     mirrorRootPath = path.variant == LayerHierarchy::Variant::Mirror
             ? path
@@ -174,7 +185,14 @@
     std::stringstream debug;
     debug << "Snapshot{" << path.toString() << name << " isVisible=" << isVisible << " {"
           << getIsVisibleReason() << "} changes=" << changes.string()
-          << " layerStack=" << outputFilter.layerStack.id << "}";
+          << " layerStack=" << outputFilter.layerStack.id << " geomLayerBounds={"
+          << geomLayerBounds.left << "," << geomLayerBounds.top << "," << geomLayerBounds.bottom
+          << "," << geomLayerBounds.right << "}"
+          << " geomLayerTransform={tx=" << geomLayerTransform.tx()
+          << ",ty=" << geomLayerTransform.ty() << "}"
+          << "}";
+    debug << " input{ touchCropId=" << touchCropId
+          << " replaceTouchableRegionWithCrop=" << inputInfo.replaceTouchableRegionWithCrop << "}";
     return debug.str();
 }
 
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
index 6fb2f57..b167d3e 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -89,10 +89,14 @@
     gui::GameMode gameMode;
     scheduler::LayerInfo::FrameRate frameRate;
     ui::Transform::RotationFlags fixedTransformHint;
+    std::optional<ui::Transform::RotationFlags> transformHint;
     bool handleSkipScreenshotFlag = false;
     int32_t frameRateSelectionPriority;
     LayerHierarchy::TraversalPath mirrorRootPath;
     bool unreachable = true;
+    uint32_t touchCropId;
+    uid_t uid;
+    pid_t pid;
     ChildState childState;
 
     static bool isOpaqueFormat(PixelFormat format);
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index a16de1b..985c6f9 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -21,10 +21,16 @@
 
 #include "LayerSnapshotBuilder.h"
 #include <gui/TraceUtils.h>
+#include <ui/FloatRect.h>
+
 #include <numeric>
+#include <optional>
+
+#include <gui/TraceUtils.h>
 #include "DisplayHardware/HWC2.h"
 #include "DisplayHardware/Hal.h"
 #include "LayerLog.h"
+#include "LayerSnapshotBuilder.h"
 #include "TimeStats/TimeStats.h"
 #include "ftl/small_map.h"
 
@@ -101,43 +107,52 @@
 }
 
 /**
+ * Returns the bounds used to fill the input frame and the touchable region.
+ *
  * Similar to getInputTransform, we need to update the bounds to include the transform.
  * This is because bounds don't include the buffer transform, where the input assumes
  * that's already included.
  */
-Rect getInputBounds(const LayerSnapshot& snapshot) {
-    if (!snapshot.hasBufferOrSidebandStream()) {
-        return snapshot.croppedBufferSize;
+std::pair<FloatRect, bool> getInputBounds(const LayerSnapshot& snapshot, bool fillParentBounds) {
+    FloatRect inputBounds = snapshot.croppedBufferSize.toFloatRect();
+    if (snapshot.hasBufferOrSidebandStream() && snapshot.croppedBufferSize.isValid() &&
+        snapshot.localTransform.getType() != ui::Transform::IDENTITY) {
+        inputBounds = snapshot.localTransform.transform(inputBounds);
     }
 
-    if (snapshot.localTransform.getType() == ui::Transform::IDENTITY ||
-        !snapshot.croppedBufferSize.isValid()) {
-        return snapshot.croppedBufferSize;
+    bool inputBoundsValid = snapshot.croppedBufferSize.isValid();
+    if (!inputBoundsValid) {
+        /**
+         * Input bounds are based on the layer crop or buffer size. But if we are using
+         * the layer bounds as the input bounds (replaceTouchableRegionWithCrop flag) then
+         * we can use the parent bounds as the input bounds if the layer does not have buffer
+         * or a crop. We want to unify this logic but because of compat reasons we cannot always
+         * use the parent bounds. A layer without a buffer can get input. So when a window is
+         * initially added, its touchable region can fill its parent layer bounds and that can
+         * have negative consequences.
+         */
+        inputBounds = fillParentBounds ? snapshot.geomLayerBounds : FloatRect{};
     }
-    return snapshot.localTransform.transform(snapshot.croppedBufferSize);
+
+    // Clamp surface inset to the input bounds.
+    const float inset = static_cast<float>(snapshot.inputInfo.surfaceInset);
+    const float xSurfaceInset = std::clamp(inset, 0.f, inputBounds.getWidth() / 2.f);
+    const float ySurfaceInset = std::clamp(inset, 0.f, inputBounds.getHeight() / 2.f);
+
+    // Apply the insets to the input bounds.
+    inputBounds.left += xSurfaceInset;
+    inputBounds.top += ySurfaceInset;
+    inputBounds.right -= xSurfaceInset;
+    inputBounds.bottom -= ySurfaceInset;
+    return {inputBounds, inputBoundsValid};
 }
 
-void fillInputFrameInfo(gui::WindowInfo& info, const ui::Transform& screenToDisplay,
-                        const LayerSnapshot& snapshot) {
-    Rect tmpBounds = getInputBounds(snapshot);
-    if (!tmpBounds.isValid()) {
-        info.touchableRegion.clear();
-        // A layer could have invalid input bounds and still expect to receive touch input if it has
-        // replaceTouchableRegionWithCrop. For that case, the input transform needs to be calculated
-        // correctly to determine the coordinate space for input events. Use an empty rect so that
-        // the layer will receive input in its own layer space.
-        tmpBounds = Rect::EMPTY_RECT;
-    }
-
+Rect getInputBoundsInDisplaySpace(const LayerSnapshot& snapshot, const FloatRect& insetBounds,
+                                  const ui::Transform& screenToDisplay) {
     // InputDispatcher works in the display device's coordinate space. Here, we calculate the
     // frame and transform used for the layer, which determines the bounds and the coordinate space
     // within which the layer will receive input.
-    //
-    // The coordinate space within which each of the bounds are specified is explicitly documented
-    // in the variable name. For example "inputBoundsInLayer" is specified in layer space. A
-    // Transform converts one coordinate space to another, which is apparent in its naming. For
-    // example, "layerToDisplay" transforms layer space to display space.
-    //
+
     // Coordinate space definitions:
     //   - display: The display device's coordinate space. Correlates to pixels on the display.
     //   - screen: The post-rotation coordinate space for the display, a.k.a. logical display space.
@@ -145,37 +160,34 @@
     //   - input: The coordinate space in which this layer will receive input events. This could be
     //            different than layer space if a surfaceInset is used, which changes the origin
     //            of the input space.
-    const FloatRect inputBoundsInLayer = tmpBounds.toFloatRect();
-
-    // Clamp surface inset to the input bounds.
-    const auto surfaceInset = static_cast<float>(info.surfaceInset);
-    const float xSurfaceInset =
-            std::max(0.f, std::min(surfaceInset, inputBoundsInLayer.getWidth() / 2.f));
-    const float ySurfaceInset =
-            std::max(0.f, std::min(surfaceInset, inputBoundsInLayer.getHeight() / 2.f));
-
-    // Apply the insets to the input bounds.
-    const FloatRect insetBoundsInLayer(inputBoundsInLayer.left + xSurfaceInset,
-                                       inputBoundsInLayer.top + ySurfaceInset,
-                                       inputBoundsInLayer.right - xSurfaceInset,
-                                       inputBoundsInLayer.bottom - ySurfaceInset);
 
     // Crop the input bounds to ensure it is within the parent's bounds.
-    const FloatRect croppedInsetBoundsInLayer =
-            snapshot.geomLayerBounds.intersect(insetBoundsInLayer);
+    const FloatRect croppedInsetBoundsInLayer = snapshot.geomLayerBounds.intersect(insetBounds);
 
     const ui::Transform layerToScreen = getInputTransform(snapshot);
     const ui::Transform layerToDisplay = screenToDisplay * layerToScreen;
 
-    const Rect roundedFrameInDisplay{layerToDisplay.transform(croppedInsetBoundsInLayer)};
+    return Rect{layerToDisplay.transform(croppedInsetBoundsInLayer)};
+}
+
+void fillInputFrameInfo(gui::WindowInfo& info, const ui::Transform& screenToDisplay,
+                        const LayerSnapshot& snapshot) {
+    auto [inputBounds, inputBoundsValid] = getInputBounds(snapshot, /*fillParentBounds=*/false);
+    if (!inputBoundsValid) {
+        info.touchableRegion.clear();
+    }
+
+    const Rect roundedFrameInDisplay =
+            getInputBoundsInDisplaySpace(snapshot, inputBounds, screenToDisplay);
     info.frameLeft = roundedFrameInDisplay.left;
     info.frameTop = roundedFrameInDisplay.top;
     info.frameRight = roundedFrameInDisplay.right;
     info.frameBottom = roundedFrameInDisplay.bottom;
 
     ui::Transform inputToLayer;
-    inputToLayer.set(insetBoundsInLayer.left, insetBoundsInLayer.top);
-    const ui::Transform inputToDisplay = layerToDisplay * inputToLayer;
+    inputToLayer.set(inputBounds.left, inputBounds.top);
+    const ui::Transform layerToScreen = getInputTransform(snapshot);
+    const ui::Transform inputToDisplay = screenToDisplay * layerToScreen * inputToLayer;
 
     // InputDispatcher expects a display-to-input transform.
     info.transform = inputToDisplay.inverse();
@@ -435,23 +447,36 @@
         }
     }
 
+    // Update touchable region crops outside the main update pass. This is because a layer could be
+    // cropped by any other layer and it requires both snapshots to be updated.
+    updateTouchableRegionCrop(args);
+
     const bool hasUnreachableSnapshots = sortSnapshotsByZ(args);
     clearChanges(mRootSnapshot);
 
-    // Destroy unreachable snapshots
-    if (!hasUnreachableSnapshots) {
+    // Destroy unreachable snapshots for clone layers. And destroy snapshots for non-clone
+    // layers if the layer have been destroyed.
+    // TODO(b/238781169) consider making clone layer ids stable as well
+    if (!hasUnreachableSnapshots && args.layerLifecycleManager.getDestroyedLayers().empty()) {
         return;
     }
 
+    std::unordered_set<uint32_t> destroyedLayerIds;
+    for (auto& destroyedLayer : args.layerLifecycleManager.getDestroyedLayers()) {
+        destroyedLayerIds.insert(destroyedLayer->id);
+    }
+
     auto it = mSnapshots.begin();
     while (it < mSnapshots.end()) {
         auto& traversalPath = it->get()->path;
-        if (!it->get()->unreachable) {
+        if (!it->get()->unreachable &&
+            destroyedLayerIds.find(traversalPath.id) == destroyedLayerIds.end()) {
             it++;
             continue;
         }
 
         mIdToSnapshot.erase(traversalPath);
+        mNeedsTouchableRegionCrop.erase(traversalPath);
         mSnapshots.back()->globalZ = it->get()->globalZ;
         std::iter_swap(it, mSnapshots.end() - 1);
         mSnapshots.erase(mSnapshots.end() - 1);
@@ -542,7 +567,7 @@
     mResortSnapshots = false;
 
     for (auto& snapshot : mSnapshots) {
-        snapshot->unreachable = true;
+        snapshot->unreachable = snapshot->path.isClone();
     }
 
     size_t globalZ = 0;
@@ -642,12 +667,15 @@
     snapshot.relativeLayerMetadata.mMap.clear();
 }
 
-uint32_t getDisplayRotationFlags(
-        const display::DisplayMap<ui::LayerStack, frontend::DisplayInfo>& displays,
-        const ui::LayerStack& layerStack) {
-    static frontend::DisplayInfo sDefaultDisplayInfo = {.isPrimary = false};
-    auto display = displays.get(layerStack).value_or(sDefaultDisplayInfo).get();
-    return display.isPrimary ? display.rotationFlags : 0;
+// TODO (b/259407931): Remove.
+uint32_t getPrimaryDisplayRotationFlags(
+        const display::DisplayMap<ui::LayerStack, frontend::DisplayInfo>& displays) {
+    for (auto& [_, display] : displays) {
+        if (display.isPrimary) {
+            return display.rotationFlags;
+        }
+    }
+    return 0;
 }
 
 void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& args,
@@ -674,8 +702,7 @@
             ? requested.layerStack
             : parentSnapshot.outputFilter.layerStack;
 
-    uint32_t displayRotationFlags =
-            getDisplayRotationFlags(args.displays, snapshot.outputFilter.layerStack);
+    uint32_t primaryDisplayRotationFlags = getPrimaryDisplayRotationFlags(args.displays);
     const bool forceUpdate = args.forceUpdate == ForceUpdateFlags::ALL ||
             snapshot.changes.any(RequestedLayerState::Changes::Visibility |
                                  RequestedLayerState::Changes::Created);
@@ -689,7 +716,7 @@
                 : Fence::NO_FENCE;
         snapshot.buffer =
                 requested.externalTexture ? requested.externalTexture->getBuffer() : nullptr;
-        snapshot.bufferSize = requested.getBufferSize(displayRotationFlags);
+        snapshot.bufferSize = requested.getBufferSize(primaryDisplayRotationFlags);
         snapshot.geomBufferSize = snapshot.bufferSize;
         snapshot.croppedBufferSize = requested.getCroppedBufferSize(snapshot.bufferSize);
         snapshot.dataspace = requested.dataspace;
@@ -707,8 +734,8 @@
         snapshot.sidebandStream = requested.sidebandStream;
         snapshot.transparentRegionHint = requested.transparentRegion;
         snapshot.color.rgb = requested.getColor().rgb;
-        snapshot.currentSdrHdrRatio = requested.currentSdrHdrRatio;
-        snapshot.desiredSdrHdrRatio = requested.desiredSdrHdrRatio;
+        snapshot.currentHdrSdrRatio = requested.currentHdrSdrRatio;
+        snapshot.desiredHdrSdrRatio = requested.desiredHdrSdrRatio;
     }
 
     if (snapshot.isHiddenByPolicyFromParent &&
@@ -726,6 +753,8 @@
         // If root layer, use the layer stack otherwise get the parent's layer stack.
         snapshot.color.a = parentSnapshot.color.a * requested.color.a;
         snapshot.alpha = snapshot.color.a;
+        snapshot.inputInfo.alpha = snapshot.color.a;
+
         snapshot.isSecure =
                 parentSnapshot.isSecure || (requested.flags & layer_state_t::eLayerSecure);
         snapshot.isTrustedOverlay = parentSnapshot.isTrustedOverlay || requested.isTrustedOverlay;
@@ -744,15 +773,28 @@
         snapshot.gameMode = requested.metadata.has(gui::METADATA_GAME_MODE)
                 ? requested.gameMode
                 : parentSnapshot.gameMode;
-        snapshot.fixedTransformHint = requested.fixedTransformHint != ui::Transform::ROT_INVALID
-                ? requested.fixedTransformHint
-                : parentSnapshot.fixedTransformHint;
         // Display mirrors are always placed in a VirtualDisplay so we never want to capture layers
         // marked as skip capture
         snapshot.handleSkipScreenshotFlag = parentSnapshot.handleSkipScreenshotFlag ||
                 (requested.layerStackToMirror != ui::INVALID_LAYER_STACK);
     }
 
+    if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::AffectsChildren) ||
+        args.displayChanges) {
+        snapshot.fixedTransformHint = requested.fixedTransformHint != ui::Transform::ROT_INVALID
+                ? requested.fixedTransformHint
+                : parentSnapshot.fixedTransformHint;
+
+        if (snapshot.fixedTransformHint != ui::Transform::ROT_INVALID) {
+            snapshot.transformHint = snapshot.fixedTransformHint;
+        } else {
+            const auto display = args.displays.get(snapshot.outputFilter.layerStack);
+            snapshot.transformHint = display.has_value()
+                    ? std::make_optional<>(display->get().transformHint)
+                    : std::nullopt;
+        }
+    }
+
     if (forceUpdate ||
         snapshot.changes.any(RequestedLayerState::Changes::FrameRate |
                              RequestedLayerState::Changes::Hierarchy)) {
@@ -776,7 +818,8 @@
         snapshot.frameRateSelectionPriority = requested.frameRateSelectionPriority;
     }
 
-    if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::Content)) {
+    if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::Content) ||
+        snapshot.changes.any(RequestedLayerState::Changes::AffectsChildren)) {
         snapshot.color.rgb = requested.getColor().rgb;
         snapshot.isColorspaceAgnostic = requested.colorSpaceAgnostic;
         snapshot.backgroundBlurRadius = args.supportsBlur
@@ -792,7 +835,7 @@
     if (forceUpdate ||
         snapshot.changes.any(RequestedLayerState::Changes::Hierarchy |
                              RequestedLayerState::Changes::Geometry)) {
-        updateLayerBounds(snapshot, requested, parentSnapshot, displayRotationFlags);
+        updateLayerBounds(snapshot, requested, parentSnapshot, primaryDisplayRotationFlags);
         updateRoundedCorner(snapshot, requested, parentSnapshot);
     }
 
@@ -863,10 +906,10 @@
 void LayerSnapshotBuilder::updateLayerBounds(LayerSnapshot& snapshot,
                                              const RequestedLayerState& requested,
                                              const LayerSnapshot& parentSnapshot,
-                                             uint32_t displayRotationFlags) {
+                                             uint32_t primaryDisplayRotationFlags) {
     snapshot.croppedBufferSize = requested.getCroppedBufferSize(snapshot.bufferSize);
     snapshot.geomCrop = requested.crop;
-    snapshot.localTransform = requested.getTransform(displayRotationFlags);
+    snapshot.localTransform = requested.getTransform(primaryDisplayRotationFlags);
     snapshot.localTransformInverse = snapshot.localTransform.inverse();
     snapshot.geomLayerTransform = parentSnapshot.geomLayerTransform * snapshot.localTransform;
     const bool transformWasInvalid = snapshot.invalidTransform;
@@ -880,14 +923,14 @@
                                    requestedT.dsdy(), requestedT.dtdx(), requestedT.dtdy());
         std::string bufferDebug;
         if (requested.externalTexture) {
-            auto unRotBuffer = requested.getUnrotatedBufferSize(displayRotationFlags);
+            auto unRotBuffer = requested.getUnrotatedBufferSize(primaryDisplayRotationFlags);
             auto& destFrame = requested.destinationFrame;
             bufferDebug = base::StringPrintf(" buffer={%d,%d}  displayRot=%d"
                                              " destFrame={%d,%d,%d,%d} unRotBuffer={%d,%d}",
                                              requested.externalTexture->getWidth(),
                                              requested.externalTexture->getHeight(),
-                                             displayRotationFlags, destFrame.left, destFrame.top,
-                                             destFrame.right, destFrame.bottom,
+                                             primaryDisplayRotationFlags, destFrame.left,
+                                             destFrame.top, destFrame.right, destFrame.bottom,
                                              unRotBuffer.getHeight(), unRotBuffer.getWidth());
         }
         ALOGW("Resetting transform for %s because it is invalid.%s%s",
@@ -960,9 +1003,11 @@
         snapshot.inputInfo.ownerUid = static_cast<int32_t>(requested.ownerUid);
         snapshot.inputInfo.ownerPid = requested.ownerPid;
     }
+    snapshot.touchCropId = requested.touchCropId;
 
     snapshot.inputInfo.id = static_cast<int32_t>(snapshot.uniqueSequence);
     snapshot.inputInfo.displayId = static_cast<int32_t>(snapshot.outputFilter.layerStack.id);
+    updateVisibility(snapshot, snapshot.isVisible);
     if (!needsInputInfo(snapshot, requested)) {
         return;
     }
@@ -1007,13 +1052,13 @@
     }
 
     auto cropLayerSnapshot = getSnapshot(requested.touchCropId);
-    if (snapshot.inputInfo.replaceTouchableRegionWithCrop) {
-        const Rect bounds(cropLayerSnapshot ? cropLayerSnapshot->transformedBounds
-                                            : snapshot.transformedBounds);
-        snapshot.inputInfo.touchableRegion = Region(displayInfo.transform.transform(bounds));
-    } else if (cropLayerSnapshot) {
-        snapshot.inputInfo.touchableRegion = snapshot.inputInfo.touchableRegion.intersect(
-                displayInfo.transform.transform(Rect{cropLayerSnapshot->transformedBounds}));
+    if (cropLayerSnapshot) {
+        mNeedsTouchableRegionCrop.insert(path);
+    } else if (snapshot.inputInfo.replaceTouchableRegionWithCrop) {
+        FloatRect inputBounds = getInputBounds(snapshot, /*fillParentBounds=*/true).first;
+        Rect inputBoundsInDisplaySpace =
+                getInputBoundsInDisplaySpace(snapshot, inputBounds, displayInfo.transform);
+        snapshot.inputInfo.touchableRegion = Region(inputBoundsInDisplaySpace);
     }
 
     // Inherit the trusted state from the parent hierarchy, but don't clobber the trusted state
@@ -1026,12 +1071,11 @@
     // touches from going outside the cloned area.
     if (path.isClone()) {
         snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::CLONE;
-        auto clonedRootSnapshot = getSnapshot(snapshot.mirrorRootPath);
-        if (clonedRootSnapshot) {
-            const Rect rect =
-                    displayInfo.transform.transform(Rect{clonedRootSnapshot->transformedBounds});
-            snapshot.inputInfo.touchableRegion = snapshot.inputInfo.touchableRegion.intersect(rect);
-        }
+        // Cloned layers shouldn't handle watch outside since their z order is not determined by
+        // WM or the client.
+        snapshot.inputInfo.inputConfig.clear(gui::WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH);
+
+        mNeedsTouchableRegionCrop.insert(path);
     }
 }
 
@@ -1077,4 +1121,77 @@
     }
 }
 
+void LayerSnapshotBuilder::updateTouchableRegionCrop(const Args& args) {
+    if (mNeedsTouchableRegionCrop.empty()) {
+        return;
+    }
+
+    static constexpr ftl::Flags<RequestedLayerState::Changes> AFFECTS_INPUT =
+            RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Created |
+            RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Geometry |
+            RequestedLayerState::Changes::Input;
+
+    if (args.forceUpdate != ForceUpdateFlags::ALL &&
+        !args.layerLifecycleManager.getGlobalChanges().any(AFFECTS_INPUT)) {
+        return;
+    }
+
+    for (auto& path : mNeedsTouchableRegionCrop) {
+        frontend::LayerSnapshot* snapshot = getSnapshot(path);
+        if (!snapshot) {
+            continue;
+        }
+        const std::optional<frontend::DisplayInfo> displayInfoOpt =
+                args.displays.get(snapshot->outputFilter.layerStack);
+        static frontend::DisplayInfo sDefaultInfo = {.isSecure = false};
+        auto displayInfo = displayInfoOpt.value_or(sDefaultInfo);
+
+        bool needsUpdate =
+                args.forceUpdate == ForceUpdateFlags::ALL || snapshot->changes.any(AFFECTS_INPUT);
+        auto cropLayerSnapshot = getSnapshot(snapshot->touchCropId);
+        needsUpdate =
+                needsUpdate || (cropLayerSnapshot && cropLayerSnapshot->changes.any(AFFECTS_INPUT));
+        auto clonedRootSnapshot = path.isClone() ? getSnapshot(snapshot->mirrorRootPath) : nullptr;
+        needsUpdate = needsUpdate ||
+                (clonedRootSnapshot && clonedRootSnapshot->changes.any(AFFECTS_INPUT));
+
+        if (!needsUpdate) {
+            continue;
+        }
+
+        if (snapshot->inputInfo.replaceTouchableRegionWithCrop) {
+            Rect inputBoundsInDisplaySpace;
+            if (!cropLayerSnapshot) {
+                FloatRect inputBounds = getInputBounds(*snapshot, /*fillParentBounds=*/true).first;
+                inputBoundsInDisplaySpace =
+                        getInputBoundsInDisplaySpace(*snapshot, inputBounds, displayInfo.transform);
+            } else {
+                FloatRect inputBounds =
+                        getInputBounds(*cropLayerSnapshot, /*fillParentBounds=*/true).first;
+                inputBoundsInDisplaySpace =
+                        getInputBoundsInDisplaySpace(*cropLayerSnapshot, inputBounds,
+                                                     displayInfo.transform);
+            }
+            snapshot->inputInfo.touchableRegion = Region(inputBoundsInDisplaySpace);
+        } else if (cropLayerSnapshot) {
+            FloatRect inputBounds =
+                    getInputBounds(*cropLayerSnapshot, /*fillParentBounds=*/true).first;
+            Rect inputBoundsInDisplaySpace =
+                    getInputBoundsInDisplaySpace(*cropLayerSnapshot, inputBounds,
+                                                 displayInfo.transform);
+            snapshot->inputInfo.touchableRegion = snapshot->inputInfo.touchableRegion.intersect(
+                    displayInfo.transform.transform(inputBoundsInDisplaySpace));
+        }
+
+        // If the layer is a clone, we need to crop the input region to cloned root to prevent
+        // touches from going outside the cloned area.
+        if (clonedRootSnapshot) {
+            const Rect rect =
+                    displayInfo.transform.transform(Rect{clonedRootSnapshot->transformedBounds});
+            snapshot->inputInfo.touchableRegion =
+                    snapshot->inputInfo.touchableRegion.intersect(rect);
+        }
+    }
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
index 3997a0a..148c98e 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
@@ -68,6 +68,7 @@
     void update(const Args&);
     std::vector<std::unique_ptr<LayerSnapshot>>& getSnapshots();
     LayerSnapshot* getSnapshot(uint32_t layerId) const;
+    LayerSnapshot* getSnapshot(const LayerHierarchy::TraversalPath& id) const;
 
     typedef std::function<void(const LayerSnapshot& snapshot)> ConstVisitor;
 
@@ -86,7 +87,6 @@
 
 private:
     friend class LayerSnapshotTest;
-    LayerSnapshot* getSnapshot(const LayerHierarchy::TraversalPath& id) const;
     static LayerSnapshot getRootSnapshot();
 
     // return true if we were able to successfully update the snapshots via
@@ -119,18 +119,14 @@
                                   const LayerSnapshot& parentSnapshot);
     void updateChildState(LayerSnapshot& snapshot, const LayerSnapshot& childSnapshot,
                           const Args& args);
+    void updateTouchableRegionCrop(const Args& args);
 
-    struct TraversalPathHash {
-        std::size_t operator()(const LayerHierarchy::TraversalPath& key) const {
-            uint32_t hashCode = key.id * 31;
-            if (key.mirrorRootId != UNASSIGNED_LAYER_ID) {
-                hashCode += key.mirrorRootId * 31;
-            }
-            return std::hash<size_t>{}(hashCode);
-        }
-    };
-    std::unordered_map<LayerHierarchy::TraversalPath, LayerSnapshot*, TraversalPathHash>
+    std::unordered_map<LayerHierarchy::TraversalPath, LayerSnapshot*,
+                       LayerHierarchy::TraversalPathHash>
             mIdToSnapshot;
+    // Track snapshots that needs touchable region crop from other snapshots
+    std::unordered_set<LayerHierarchy::TraversalPath, LayerHierarchy::TraversalPathHash>
+            mNeedsTouchableRegionCrop;
     std::vector<std::unique_ptr<LayerSnapshot>> mSnapshots;
     LayerSnapshot mRootSnapshot;
     bool mResortSnapshots = false;
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index e2cbe28..23bb54c 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,18 +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);
-}
-
 std::string layerIdsToString(const std::vector<uint32_t>& layerIds) {
     std::stringstream stream;
     stream << "{";
@@ -64,17 +51,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) {
@@ -109,8 +96,8 @@
     layerStack = ui::DEFAULT_LAYER_STACK;
     transformToDisplayInverse = false;
     dataspace = ui::Dataspace::UNKNOWN;
-    desiredSdrHdrRatio = 1.f;
-    currentSdrHdrRatio = 1.f;
+    desiredHdrSdrRatio = 1.f;
+    currentHdrSdrRatio = 1.f;
     dataspaceRequested = false;
     hdrMetadata.validTypes = 0;
     surfaceDamageRegion = Region::INVALID_REGION;
@@ -143,7 +130,8 @@
 void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerState) {
     const uint32_t oldFlags = flags;
     const half oldAlpha = color.a;
-    const bool hadBufferOrSideStream = hasValidBuffer() || sidebandStream != nullptr;
+    const bool hadBuffer = externalTexture != nullptr;
+    const bool hadSideStream = sidebandStream != nullptr;
     const layer_state_t& clientState = resolvedComposerState.state;
     const bool hadBlur = hasBlur();
     uint64_t clientChanges = what | layer_state_t::diff(clientState);
@@ -159,19 +147,31 @@
             changes |= RequestedLayerState::Changes::Geometry;
         }
     }
-    if (clientState.what &
-        (layer_state_t::eBufferChanged | layer_state_t::eSidebandStreamChanged)) {
-        const bool hasBufferOrSideStream = hasValidBuffer() || sidebandStream != nullptr;
-        if (hadBufferOrSideStream != hasBufferOrSideStream) {
+    if (clientState.what & layer_state_t::eBufferChanged) {
+        externalTexture = resolvedComposerState.externalTexture;
+        barrierProducerId = std::max(bufferData->producerId, barrierProducerId);
+        barrierFrameNumber = std::max(bufferData->frameNumber, barrierFrameNumber);
+        // TODO(b/277265947) log and flush transaction trace when we detect out of order updates
+
+        const bool hasBuffer = externalTexture != nullptr;
+        if (hasBuffer || hasBuffer != hadBuffer) {
+            changes |= RequestedLayerState::Changes::Buffer;
+        }
+
+        if (hasBuffer != hadBuffer) {
             changes |= RequestedLayerState::Changes::Geometry |
                     RequestedLayerState::Changes::VisibleRegion |
                     RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Input;
         }
-        if (clientState.what & layer_state_t::eBufferChanged) {
-            changes |= RequestedLayerState::Changes::Buffer;
-        }
-        if (clientState.what & layer_state_t::eSidebandStreamChanged) {
-            changes |= RequestedLayerState::Changes::SidebandStream;
+    }
+
+    if (clientState.what & layer_state_t::eSidebandStreamChanged) {
+        changes |= RequestedLayerState::Changes::SidebandStream;
+        const bool hasSideStream = sidebandStream != nullptr;
+        if (hasSideStream != hadSideStream) {
+            changes |= RequestedLayerState::Changes::Geometry |
+                    RequestedLayerState::Changes::VisibleRegion |
+                    RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Input;
         }
     }
     if (what & (layer_state_t::eAlphaChanged)) {
@@ -209,7 +209,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 +218,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 +235,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();
@@ -248,10 +246,6 @@
         // TODO(b/238781169) handle callbacks
     }
 
-    if (clientState.what & layer_state_t::eBufferChanged) {
-        externalTexture = resolvedComposerState.externalTexture;
-    }
-
     if (clientState.what & layer_state_t::ePositionChanged) {
         requestedTransform.set(x, y);
     }
@@ -334,9 +328,13 @@
 
 std::string RequestedLayerState::getDebugString() const {
     std::stringstream debug;
-    debug << "RequestedLayerState{" << name << " parent=" << layerIdToString(parentId)
-          << " relativeParent=" << layerIdToString(relativeParentId)
-          << " mirrorId=" << layerIdsToString(mirrorIds) << " handle=" << handleAlive << " z=" << z;
+    debug << "RequestedLayerState{" << name;
+    if (parentId != UNASSIGNED_LAYER_ID) debug << " parentId=" << parentId;
+    if (relativeParentId != UNASSIGNED_LAYER_ID) debug << " relativeParentId=" << relativeParentId;
+    if (!mirrorIds.empty()) debug << " mirrorId=" << layerIdsToString(mirrorIds);
+    if (!handleAlive) debug << " !handle";
+    if (z != 0) debug << " z=" << z;
+    if (layerStack.id != 0) debug << " layerStack=" << layerStack.id;
     return debug.str();
 }
 
@@ -473,6 +471,10 @@
     return hasFrameUpdate() && sidebandStream.get();
 }
 
+bool RequestedLayerState::willReleaseBufferOnLatch() const {
+    return changes.test(Changes::Buffer) && !externalTexture;
+}
+
 void RequestedLayerState::clearChanges() {
     what = 0;
     changes.clear();
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
index 6f5485d..0ef50bc 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
@@ -79,6 +79,7 @@
     bool hasFrameUpdate() const;
     bool hasReadyFrame() const;
     bool hasSidebandStreamFrame() const;
+    bool willReleaseBufferOnLatch() const;
 
     // Layer serial number.  This gives layers an explicit ordering, so we
     // have a stable sort order when their layer stack and Z-order are
@@ -105,17 +106,19 @@
     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;
+    uint64_t barrierFrameNumber = 0;
+    uint32_t barrierProducerId = 0;
 
     // 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/TransactionHandler.cpp b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
index 8629671..9cbe0bb 100644
--- a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
@@ -21,6 +21,7 @@
 
 #include <cutils/trace.h>
 #include <utils/Log.h>
+#include <utils/Trace.h>
 
 #include "TransactionHandler.h"
 
@@ -64,11 +65,62 @@
         transactionsPendingBarrier = flushPendingTransactionQueues(transactions, flushState);
     } while (lastTransactionsPendingBarrier != transactionsPendingBarrier);
 
+    applyUnsignaledBufferTransaction(transactions, flushState);
+
     mPendingTransactionCount.fetch_sub(transactions.size());
     ATRACE_INT("TransactionQueue", static_cast<int>(mPendingTransactionCount.load()));
     return transactions;
 }
 
+void TransactionHandler::applyUnsignaledBufferTransaction(
+        std::vector<TransactionState>& transactions, TransactionFlushState& flushState) {
+    if (!flushState.queueWithUnsignaledBuffer) {
+        return;
+    }
+
+    // only apply an unsignaled buffer transaction if it's the first one
+    if (!transactions.empty()) {
+        ATRACE_NAME("fence unsignaled");
+        return;
+    }
+
+    auto it = mPendingTransactionQueues.find(flushState.queueWithUnsignaledBuffer);
+    LOG_ALWAYS_FATAL_IF(it == mPendingTransactionQueues.end(),
+                        "Could not find queue with unsignaled buffer!");
+
+    auto& queue = it->second;
+    popTransactionFromPending(transactions, flushState, queue);
+    if (queue.empty()) {
+        it = mPendingTransactionQueues.erase(it);
+    }
+}
+
+void TransactionHandler::popTransactionFromPending(std::vector<TransactionState>& transactions,
+                                                   TransactionFlushState& flushState,
+                                                   std::queue<TransactionState>& queue) {
+    auto& transaction = queue.front();
+    // Transaction is ready move it from the pending queue.
+    flushState.firstTransaction = false;
+    removeFromStalledTransactions(transaction.id);
+    transactions.emplace_back(std::move(transaction));
+    queue.pop();
+
+    auto& readyToApplyTransaction = transactions.back();
+    readyToApplyTransaction.traverseStatesWithBuffers([&](const layer_state_t& state) {
+        const bool frameNumberChanged =
+                state.bufferData->flags.test(BufferData::BufferDataChange::frameNumberChanged);
+        if (frameNumberChanged) {
+            flushState.bufferLayersReadyToPresent.emplace_or_replace(state.surface.get(),
+                                                                     state.bufferData->frameNumber);
+        } else {
+            // Barrier function only used for BBQ which always includes a frame number.
+            // This value only used for barrier logic.
+            flushState.bufferLayersReadyToPresent
+                    .emplace_or_replace(state.surface.get(), std::numeric_limits<uint64_t>::max());
+        }
+    });
+}
+
 TransactionHandler::TransactionReadiness TransactionHandler::applyFilters(
         TransactionFlushState& flushState) {
     auto ready = TransactionReadiness::Ready;
@@ -79,8 +131,7 @@
             case TransactionReadiness::NotReadyBarrier:
                 return perFilterReady;
 
-            case TransactionReadiness::ReadyUnsignaled:
-            case TransactionReadiness::ReadyUnsignaledSingle:
+            case TransactionReadiness::NotReadyUnsignaled:
                 // If one of the filters allows latching an unsignaled buffer, latch this ready
                 // state.
                 ready = perFilterReady;
@@ -97,17 +148,7 @@
     int transactionsPendingBarrier = 0;
     auto it = mPendingTransactionQueues.begin();
     while (it != mPendingTransactionQueues.end()) {
-        auto& queue = it->second;
-        IBinder* queueToken = it->first.get();
-
-        // if we have already flushed a transaction with an unsignaled buffer then stop queue
-        // processing
-        if (std::find(flushState.queuesWithUnsignaledBuffers.begin(),
-                      flushState.queuesWithUnsignaledBuffers.end(),
-                      queueToken) != flushState.queuesWithUnsignaledBuffers.end()) {
-            continue;
-        }
-
+        auto& [applyToken, queue] = *it;
         while (!queue.empty()) {
             auto& transaction = queue.front();
             flushState.transaction = &transaction;
@@ -117,38 +158,14 @@
                 break;
             } else if (ready == TransactionReadiness::NotReady) {
                 break;
-            }
-
-            // Transaction is ready move it from the pending queue.
-            flushState.firstTransaction = false;
-            removeFromStalledTransactions(transaction.id);
-            transactions.emplace_back(std::move(transaction));
-            queue.pop();
-
-            // If the buffer is unsignaled, then we don't want to signal other transactions using
-            // the buffer as a barrier.
-            auto& readyToApplyTransaction = transactions.back();
-            if (ready == TransactionReadiness::Ready) {
-                readyToApplyTransaction.traverseStatesWithBuffers([&](const layer_state_t& state) {
-                    const bool frameNumberChanged = state.bufferData->flags.test(
-                            BufferData::BufferDataChange::frameNumberChanged);
-                    if (frameNumberChanged) {
-                        flushState.bufferLayersReadyToPresent
-                                .emplace_or_replace(state.surface.get(),
-                                                    state.bufferData->frameNumber);
-                    } else {
-                        // Barrier function only used for BBQ which always includes a frame number.
-                        // This value only used for barrier logic.
-                        flushState.bufferLayersReadyToPresent
-                                .emplace_or_replace(state.surface.get(),
-                                                    std::numeric_limits<uint64_t>::max());
-                    }
-                });
-            } else if (ready == TransactionReadiness::ReadyUnsignaledSingle) {
-                // Track queues with a flushed unsingaled buffer.
-                flushState.queuesWithUnsignaledBuffers.emplace_back(queueToken);
+            } else if (ready == TransactionReadiness::NotReadyUnsignaled) {
+                // We maybe able to latch this transaction if it's the only transaction
+                // ready to be applied.
+                flushState.queueWithUnsignaledBuffer = applyToken;
                 break;
             }
+            // ready == TransactionReadiness::Ready
+            popTransactionFromPending(transactions, flushState, queue);
         }
 
         if (queue.empty()) {
diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.h b/services/surfaceflinger/FrontEnd/TransactionHandler.h
index 7fc825e..865835f 100644
--- a/services/surfaceflinger/FrontEnd/TransactionHandler.h
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.h
@@ -40,14 +40,20 @@
         // Layer handles that have transactions with buffers that are ready to be applied.
         ftl::SmallMap<IBinder* /* binder address */, uint64_t /* framenumber */, 15>
                 bufferLayersReadyToPresent = {};
-        ftl::SmallVector<IBinder* /* queueToken */, 15> queuesWithUnsignaledBuffers;
+        // Tracks the queue with an unsignaled buffer. This is used to handle
+        // LatchUnsignaledConfig::AutoSingleLayer to ensure we only apply an unsignaled buffer
+        // if it's the only transaction that is ready to be applied.
+        sp<IBinder> queueWithUnsignaledBuffer = nullptr;
     };
     enum class TransactionReadiness {
-        NotReady,
-        NotReadyBarrier,
+        // Transaction is ready to be applied
         Ready,
-        ReadyUnsignaled,
-        ReadyUnsignaledSingle,
+        // Transaction has unmet conditions (fence, present time, etc) and cannot be applied.
+        NotReady,
+        // Transaction is waiting on a barrier (another buffer to be latched first)
+        NotReadyBarrier,
+        // Transaction has an unsignaled fence but can be applied if it's the only transaction
+        NotReadyUnsignaled,
     };
     using TransactionFilter = std::function<TransactionReadiness(const TransactionFlushState&)>;
 
@@ -64,6 +70,9 @@
     friend class ::android::TestableSurfaceFlinger;
 
     int flushPendingTransactionQueues(std::vector<TransactionState>&, TransactionFlushState&);
+    void applyUnsignaledBufferTransaction(std::vector<TransactionState>&, TransactionFlushState&);
+    void popTransactionFromPending(std::vector<TransactionState>&, TransactionFlushState&,
+                                   std::queue<TransactionState>&);
     TransactionReadiness applyFilters(TransactionFlushState&);
     std::unordered_map<sp<IBinder>, std::queue<TransactionState>, IListenerHash>
             mPendingTransactionQueues;
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 0f2af2f..f12aab7 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -52,8 +52,11 @@
 #include <system/graphics-base-v1.0.h>
 #include <ui/DataspaceUtils.h>
 #include <ui/DebugUtils.h>
+#include <ui/FloatRect.h>
 #include <ui/GraphicBuffer.h>
 #include <ui/PixelFormat.h>
+#include <ui/Rect.h>
+#include <ui/Transform.h>
 #include <utils/Errors.h>
 #include <utils/Log.h>
 #include <utils/NativeHandle.h>
@@ -72,6 +75,7 @@
 #include "FrontEnd/LayerCreationArgs.h"
 #include "FrontEnd/LayerHandle.h"
 #include "LayerProtoHelper.h"
+#include "MutexUtils.h"
 #include "SurfaceFlinger.h"
 #include "TimeStats/TimeStats.h"
 #include "TunnelModeEnabledReporter.h"
@@ -163,13 +167,15 @@
     mDrawingState.sequence = 0;
     mDrawingState.transform.set(0, 0);
     mDrawingState.frameNumber = 0;
+    mDrawingState.barrierFrameNumber = 0;
+    mDrawingState.producerId = 0;
+    mDrawingState.barrierProducerId = 0;
     mDrawingState.bufferTransform = 0;
     mDrawingState.transformToDisplayInverse = false;
     mDrawingState.crop.makeInvalid();
     mDrawingState.acquireFence = sp<Fence>::make(-1);
     mDrawingState.acquireFenceTime = std::make_shared<FenceTime>(mDrawingState.acquireFence);
-    mDrawingState.dataspace = ui::Dataspace::UNKNOWN;
-    mDrawingState.dataspaceRequested = false;
+    mDrawingState.dataspace = ui::Dataspace::V0_SRGB;
     mDrawingState.hdrMetadata.validTypes = 0;
     mDrawingState.surfaceDamageRegion = Region::INVALID_REGION;
     mDrawingState.cornerRadius = 0.0f;
@@ -205,7 +211,6 @@
     mPremultipliedAlpha = !(args.flags & ISurfaceComposerClient::eNonPremultiplied);
     mPotentialCursor = args.flags & ISurfaceComposerClient::eCursorWindow;
     mProtectedByApp = args.flags & ISurfaceComposerClient::eProtectedByApp;
-    mDrawingState.dataspace = ui::Dataspace::V0_SRGB;
 
     mSnapshot->sequence = sequence;
     mSnapshot->name = getDebugName();
@@ -249,7 +254,8 @@
         mFlinger->mTunnelModeEnabledReporter->decrementTunnelModeCount();
     }
     if (mHadClonedChild) {
-        mFlinger->mNumClones--;
+        auto& roots = mFlinger->mLayerMirrorRoots;
+        roots.erase(std::remove(roots.begin(), roots.end(), this), roots.end());
     }
     if (hasTrustedPresentationListener()) {
         mFlinger->mNumTrustedPresentationListeners--;
@@ -302,10 +308,12 @@
     auto layersInTree = getRootLayer()->getLayersInTree(LayerVector::StateSet::Current);
     std::sort(layersInTree.begin(), layersInTree.end());
 
-    traverse(LayerVector::StateSet::Current, [&](Layer* layer) {
-        layer->removeFromCurrentState();
-        layer->removeRelativeZ(layersInTree);
-    });
+    REQUIRE_MUTEX(mFlinger->mStateLock);
+    traverse(LayerVector::StateSet::Current,
+             [&](Layer* layer) REQUIRES(layer->mFlinger->mStateLock) {
+                 layer->removeFromCurrentState();
+                 layer->removeRelativeZ(layersInTree);
+             });
 }
 
 void Layer::addToCurrentState() {
@@ -335,6 +343,7 @@
         return nullptr;
     }
     mGetHandleCalled = true;
+    mHandleAlive = true;
     return sp<LayerHandle>::make(mFlinger, sp<Layer>::fromExisting(this));
 }
 
@@ -639,8 +648,8 @@
     snapshot->surfaceDamage = surfaceDamageRegion;
     snapshot->hasProtectedContent = isProtected();
     snapshot->dimmingEnabled = isDimmingEnabled();
-    snapshot->currentSdrHdrRatio = getCurrentSdrHdrRatio();
-    snapshot->desiredSdrHdrRatio = getDesiredSdrHdrRatio();
+    snapshot->currentHdrSdrRatio = getCurrentHdrSdrRatio();
+    snapshot->desiredHdrSdrRatio = getDesiredHdrSdrRatio();
     snapshot->cachingHint = getCachingHint();
 
     const bool usesRoundedCorners = hasRoundedCorners();
@@ -1005,10 +1014,12 @@
         mFlinger->mLayersAdded = true;
         // set up SF to handle added color layer
         if (isRemovedFromCurrentState()) {
+            MUTEX_ALIAS(mFlinger->mStateLock, mDrawingState.bgColorLayer->mFlinger->mStateLock);
             mDrawingState.bgColorLayer->onRemovedFromCurrentState();
         }
         mFlinger->setTransactionFlags(eTransactionNeeded);
     } else if (mDrawingState.bgColorLayer && alpha == 0) {
+        MUTEX_ALIAS(mFlinger->mStateLock, mDrawingState.bgColorLayer->mFlinger->mStateLock);
         mDrawingState.bgColorLayer->reparent(nullptr);
         mDrawingState.bgColorLayer = nullptr;
         return true;
@@ -1501,7 +1512,7 @@
         transformHint = ui::Transform::ROT_0;
     }
 
-    setTransformHint(transformHint);
+    setTransformHintLegacy(transformHint);
 }
 
 // ----------------------------------------------------------------------------
@@ -1639,8 +1650,8 @@
 
 void Layer::dumpOffscreenDebugInfo(std::string& result) const {
     std::string hasBuffer = hasBufferOrSidebandStream() ? " (contains buffer)" : "";
-    StringAppendF(&result, "Layer %s%s pid:%d uid:%d\n", getName().c_str(), hasBuffer.c_str(),
-                  mOwnerPid, mOwnerUid);
+    StringAppendF(&result, "Layer %s%s pid:%d uid:%d%s\n", getName().c_str(), hasBuffer.c_str(),
+                  mOwnerPid, mOwnerUid, isHandleAlive() ? " handleAlive" : "");
 }
 
 void Layer::onDisconnect() {
@@ -1649,9 +1660,9 @@
     mFlinger->mFrameTracer->onDestroy(layerId);
 }
 
-size_t Layer::getChildrenCount() const {
+size_t Layer::getDescendantCount() const {
     size_t count = 0;
-    for (const sp<Layer>& child : mCurrentChildren) {
+    for (const sp<Layer>& child : mDrawingChildren) {
         count += 1 + child->getChildrenCount();
     }
     return count;
@@ -1898,6 +1909,12 @@
     }
 }
 
+void Layer::traverseChildren(const LayerVector::Visitor& visitor) {
+    for (const sp<Layer>& child : mDrawingChildren) {
+        visitor(child.get());
+    }
+}
+
 LayerVector Layer::makeChildrenTraversalList(LayerVector::StateSet stateSet,
                                              const std::vector<Layer*>& layersInTree) {
     LOG_ALWAYS_FATAL_IF(stateSet == LayerVector::StateSet::Invalid,
@@ -2112,7 +2129,9 @@
     writeToProtoCommonState(layerProto, LayerVector::StateSet::Drawing, traceFlags);
 
     if (traceFlags & LayerTracing::TRACE_COMPOSITION) {
-        writeCompositionStateToProto(layerProto);
+        ui::LayerStack layerStack =
+                (mSnapshot) ? mSnapshot->outputFilter.layerStack : ui::INVALID_LAYER_STACK;
+        writeCompositionStateToProto(layerProto, layerStack);
     }
 
     for (const sp<Layer>& layer : mDrawingChildren) {
@@ -2122,14 +2141,15 @@
     return layerProto;
 }
 
-void Layer::writeCompositionStateToProto(LayerProto* layerProto) {
+void Layer::writeCompositionStateToProto(LayerProto* layerProto, ui::LayerStack layerStack) {
     ftl::FakeGuard guard(mFlinger->mStateLock); // Called from the main thread.
+    ftl::FakeGuard mainThreadGuard(kMainThreadContext);
 
     // Only populate for the primary display.
-    if (const auto display = mFlinger->getDefaultDisplayDeviceLocked()) {
+    if (const auto display = mFlinger->getDisplayFromLayerStack(layerStack)) {
         const auto compositionType = getCompositionType(*display);
         layerProto->set_hwc_composition_type(static_cast<HwcCompositionType>(compositionType));
-        LayerProtoHelper::writeToProto(getVisibleRegion(display.get()),
+        LayerProtoHelper::writeToProto(getVisibleRegion(display),
                                        [&]() { return layerProto->mutable_visible_region(); });
     }
 }
@@ -2297,62 +2317,21 @@
 }
 
 void Layer::fillInputFrameInfo(WindowInfo& info, const ui::Transform& screenToDisplay) {
-    Rect tmpBounds = getInputBounds();
-    if (!tmpBounds.isValid()) {
+    auto [inputBounds, inputBoundsValid] = getInputBounds(/*fillParentBounds=*/false);
+    if (!inputBoundsValid) {
         info.touchableRegion.clear();
-        // A layer could have invalid input bounds and still expect to receive touch input if it has
-        // replaceTouchableRegionWithCrop. For that case, the input transform needs to be calculated
-        // correctly to determine the coordinate space for input events. Use an empty rect so that
-        // the layer will receive input in its own layer space.
-        tmpBounds = Rect::EMPTY_RECT;
     }
 
-    // InputDispatcher works in the display device's coordinate space. Here, we calculate the
-    // frame and transform used for the layer, which determines the bounds and the coordinate space
-    // within which the layer will receive input.
-    //
-    // The coordinate space within which each of the bounds are specified is explicitly documented
-    // in the variable name. For example "inputBoundsInLayer" is specified in layer space. A
-    // Transform converts one coordinate space to another, which is apparent in its naming. For
-    // example, "layerToDisplay" transforms layer space to display space.
-    //
-    // Coordinate space definitions:
-    //   - display: The display device's coordinate space. Correlates to pixels on the display.
-    //   - screen: The post-rotation coordinate space for the display, a.k.a. logical display space.
-    //   - layer: The coordinate space of this layer.
-    //   - input: The coordinate space in which this layer will receive input events. This could be
-    //            different than layer space if a surfaceInset is used, which changes the origin
-    //            of the input space.
-    const FloatRect inputBoundsInLayer = tmpBounds.toFloatRect();
-
-    // Clamp surface inset to the input bounds.
-    const auto surfaceInset = static_cast<float>(info.surfaceInset);
-    const float xSurfaceInset =
-            std::max(0.f, std::min(surfaceInset, inputBoundsInLayer.getWidth() / 2.f));
-    const float ySurfaceInset =
-            std::max(0.f, std::min(surfaceInset, inputBoundsInLayer.getHeight() / 2.f));
-
-    // Apply the insets to the input bounds.
-    const FloatRect insetBoundsInLayer(inputBoundsInLayer.left + xSurfaceInset,
-                                       inputBoundsInLayer.top + ySurfaceInset,
-                                       inputBoundsInLayer.right - xSurfaceInset,
-                                       inputBoundsInLayer.bottom - ySurfaceInset);
-
-    // Crop the input bounds to ensure it is within the parent's bounds.
-    const FloatRect croppedInsetBoundsInLayer = mBounds.intersect(insetBoundsInLayer);
-
-    const ui::Transform layerToScreen = getInputTransform();
-    const ui::Transform layerToDisplay = screenToDisplay * layerToScreen;
-
-    const Rect roundedFrameInDisplay{layerToDisplay.transform(croppedInsetBoundsInLayer)};
+    const Rect roundedFrameInDisplay = getInputBoundsInDisplaySpace(inputBounds, screenToDisplay);
     info.frameLeft = roundedFrameInDisplay.left;
     info.frameTop = roundedFrameInDisplay.top;
     info.frameRight = roundedFrameInDisplay.right;
     info.frameBottom = roundedFrameInDisplay.bottom;
 
     ui::Transform inputToLayer;
-    inputToLayer.set(insetBoundsInLayer.left, insetBoundsInLayer.top);
-    const ui::Transform inputToDisplay = layerToDisplay * inputToLayer;
+    inputToLayer.set(inputBounds.left, inputBounds.top);
+    const ui::Transform layerToScreen = getInputTransform();
+    const ui::Transform inputToDisplay = screenToDisplay * layerToScreen * inputToLayer;
 
     // InputDispatcher expects a display-to-input transform.
     info.transform = inputToDisplay.inverse();
@@ -2464,16 +2443,7 @@
         info.inputConfig |= WindowInfo::InputConfig::NOT_TOUCHABLE;
     }
 
-    // For compatibility reasons we let layers which can receive input
-    // receive input before they have actually submitted a buffer. Because
-    // of this we use canReceiveInput instead of isVisible to check the
-    // policy-visibility, ignoring the buffer state. However for layers with
-    // hasInputInfo()==false we can use the real visibility state.
-    // We are just using these layers for occlusion detection in
-    // InputDispatcher, and obviously if they aren't visible they can't occlude
-    // anything.
-    const bool visible = hasInputInfo() ? canReceiveInput() : isVisible();
-    info.setInputConfig(WindowInfo::InputConfig::NOT_VISIBLE, !visible);
+    info.setInputConfig(WindowInfo::InputConfig::NOT_VISIBLE, !isVisibleForInput());
 
     info.alpha = getAlpha();
     fillTouchOcclusionMode(info);
@@ -2485,13 +2455,23 @@
         info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT;
     }
 
-    auto cropLayer = mDrawingState.touchableRegionCrop.promote();
+    sp<Layer> cropLayer = mDrawingState.touchableRegionCrop.promote();
     if (info.replaceTouchableRegionWithCrop) {
-        const Rect bounds(cropLayer ? cropLayer->mScreenBounds : mScreenBounds);
-        info.touchableRegion = Region(displayTransform.transform(bounds));
+        Rect inputBoundsInDisplaySpace;
+        if (!cropLayer) {
+            FloatRect inputBounds = getInputBounds(/*fillParentBounds=*/true).first;
+            inputBoundsInDisplaySpace = getInputBoundsInDisplaySpace(inputBounds, displayTransform);
+        } else {
+            FloatRect inputBounds = cropLayer->getInputBounds(/*fillParentBounds=*/true).first;
+            inputBoundsInDisplaySpace =
+                    cropLayer->getInputBoundsInDisplaySpace(inputBounds, displayTransform);
+        }
+        info.touchableRegion = Region(inputBoundsInDisplaySpace);
     } else if (cropLayer != nullptr) {
-        info.touchableRegion = info.touchableRegion.intersect(
-                displayTransform.transform(Rect{cropLayer->mScreenBounds}));
+        FloatRect inputBounds = cropLayer->getInputBounds(/*fillParentBounds=*/true).first;
+        Rect inputBoundsInDisplaySpace =
+                cropLayer->getInputBoundsInDisplaySpace(inputBounds, displayTransform);
+        info.touchableRegion = info.touchableRegion.intersect(inputBoundsInDisplaySpace);
     }
 
     // Inherit the trusted state from the parent hierarchy, but don't clobber the trusted state
@@ -2513,6 +2493,27 @@
     return info;
 }
 
+Rect Layer::getInputBoundsInDisplaySpace(const FloatRect& inputBounds,
+                                         const ui::Transform& screenToDisplay) {
+    // InputDispatcher works in the display device's coordinate space. Here, we calculate the
+    // frame and transform used for the layer, which determines the bounds and the coordinate space
+    // within which the layer will receive input.
+
+    // Coordinate space definitions:
+    //   - display: The display device's coordinate space. Correlates to pixels on the display.
+    //   - screen: The post-rotation coordinate space for the display, a.k.a. logical display space.
+    //   - layer: The coordinate space of this layer.
+    //   - input: The coordinate space in which this layer will receive input events. This could be
+    //            different than layer space if a surfaceInset is used, which changes the origin
+    //            of the input space.
+
+    // Crop the input bounds to ensure it is within the parent's bounds.
+    const FloatRect croppedInputBounds = mBounds.intersect(inputBounds);
+    const ui::Transform layerToScreen = getInputTransform();
+    const ui::Transform layerToDisplay = screenToDisplay * layerToScreen;
+    return Rect{layerToDisplay.transform(croppedInputBounds)};
+}
+
 sp<Layer> Layer::getClonedRoot() {
     if (mClonedChild != nullptr) {
         return sp<Layer>::fromExisting(this);
@@ -2552,7 +2553,10 @@
     return outputLayer ? outputLayer->getState().visibleRegion : Region();
 }
 
-void Layer::setInitialValuesForClone(const sp<Layer>& clonedFrom) {
+void Layer::setInitialValuesForClone(const sp<Layer>& clonedFrom, uint32_t mirrorRootId) {
+    mSnapshot->path.id = clonedFrom->getSequence();
+    mSnapshot->path.mirrorRootId = mirrorRootId;
+
     cloneDrawingState(clonedFrom.get());
     mClonedFrom = clonedFrom;
     mPremultipliedAlpha = clonedFrom->mPremultipliedAlpha;
@@ -2591,7 +2595,7 @@
     mDrawingState.inputInfo = tmpInputInfo;
 }
 
-void Layer::updateMirrorInfo() {
+bool Layer::updateMirrorInfo(const std::deque<Layer*>& cloneRootsPendingUpdates) {
     if (mClonedChild == nullptr || !mClonedChild->isClonedFromAlive()) {
         // If mClonedChild is null, there is nothing to mirror. If isClonedFromAlive returns false,
         // it means that there is a clone, but the layer it was cloned from has been destroyed. In
@@ -2599,7 +2603,7 @@
         // destroyed. The root, this layer, will still be around since the client can continue
         // to hold a reference, but no cloned layers will be displayed.
         mClonedChild = nullptr;
-        return;
+        return true;
     }
 
     std::map<sp<Layer>, sp<Layer>> clonedLayersMap;
@@ -2614,6 +2618,13 @@
     mClonedChild->updateClonedDrawingState(clonedLayersMap);
     mClonedChild->updateClonedChildren(sp<Layer>::fromExisting(this), clonedLayersMap);
     mClonedChild->updateClonedRelatives(clonedLayersMap);
+
+    for (Layer* root : cloneRootsPendingUpdates) {
+        if (clonedLayersMap.find(sp<Layer>::fromExisting(root)) != clonedLayersMap.end()) {
+            return false;
+        }
+    }
+    return true;
 }
 
 void Layer::updateClonedDrawingState(std::map<sp<Layer>, sp<Layer>>& clonedLayersMap) {
@@ -2653,7 +2664,7 @@
         }
         sp<Layer> clonedChild = clonedLayersMap[child];
         if (clonedChild == nullptr) {
-            clonedChild = child->createClone();
+            clonedChild = child->createClone(mirrorRoot->getSequence());
             clonedLayersMap[child] = clonedChild;
         }
         addChildToDrawing(clonedChild);
@@ -2761,7 +2772,7 @@
 void Layer::setClonedChild(const sp<Layer>& clonedChild) {
     mClonedChild = clonedChild;
     mHadClonedChild = true;
-    mFlinger->mNumClones++;
+    mFlinger->mLayerMirrorRoots.push_back(this);
 }
 
 bool Layer::setDropInputMode(gui::DropInputMode mode) {
@@ -2795,7 +2806,8 @@
                               currentMaxAcquiredBufferCount);
 }
 
-void Layer::onLayerDisplayed(ftl::SharedFuture<FenceResult> futureFenceResult) {
+void Layer::onLayerDisplayed(ftl::SharedFuture<FenceResult> futureFenceResult,
+                             ui::LayerStack layerStack) {
     // If we are displayed on multiple displays in a single composition cycle then we would
     // need to do careful tracking to enable the use of the mLastClientCompositionFence.
     //  For example we can only use it if all the displays are client comp, and we need
@@ -2825,8 +2837,7 @@
     // transaction doesn't need a previous release fence.
     sp<CallbackHandle> ch;
     for (auto& handle : mDrawingState.callbackHandles) {
-        if (handle->releasePreviousBuffer &&
-            mDrawingState.releaseBufferEndpoint == handle->listener) {
+        if (handle->releasePreviousBuffer && mPreviousReleaseBufferEndpoint == handle->listener) {
             ch = handle;
             break;
         }
@@ -2842,6 +2853,7 @@
         ch->previousReleaseFences.emplace_back(std::move(futureFenceResult));
         ch->name = mName;
     }
+    mPreviouslyPresentedLayerStacks.push_back(layerStack);
 }
 
 void Layer::onSurfaceFrameCreated(
@@ -2865,9 +2877,13 @@
 
 void Layer::releasePendingBuffer(nsecs_t dequeueReadyTime) {
     for (const auto& handle : mDrawingState.callbackHandles) {
-        handle->transformHint = mSkipReportingTransformHint
-                ? std::nullopt
-                : std::make_optional<uint32_t>(mTransformHint);
+        if (mFlinger->mLayerLifecycleManagerEnabled) {
+            handle->transformHint = mTransformHint;
+        } else {
+            handle->transformHint = mSkipReportingTransformHint
+                    ? std::nullopt
+                    : std::make_optional<uint32_t>(mTransformHintLegacy);
+        }
         handle->dequeueReadyTime = dequeueReadyTime;
         handle->currentMaxAcquiredBufferCount =
                 mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mOwnerUid);
@@ -2876,8 +2892,7 @@
     }
 
     for (auto& handle : mDrawingState.callbackHandles) {
-        if (handle->releasePreviousBuffer &&
-            mDrawingState.releaseBufferEndpoint == handle->listener) {
+        if (handle->releasePreviousBuffer && mPreviousReleaseBufferEndpoint == handle->listener) {
             handle->previousReleaseCallbackId = mPreviousReleaseCallbackId;
             break;
         }
@@ -2970,7 +2985,7 @@
     if (mDrawingState.bufferTransform & ui::Transform::ROT_90) {
         std::swap(bufferWidth, bufferHeight);
     }
-    uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags();
+    uint32_t invTransform = SurfaceFlinger::getActiveDisplayRotationFlags();
     if (mDrawingState.transformToDisplayInverse) {
         if (invTransform & ui::Transform::ROT_90) {
             std::swap(bufferWidth, bufferHeight);
@@ -3014,14 +3029,22 @@
     return true;
 }
 
+void Layer::resetDrawingStateBufferInfo() {
+    mDrawingState.producerId = 0;
+    mDrawingState.frameNumber = 0;
+    mDrawingState.releaseBufferListener = nullptr;
+    mDrawingState.buffer = nullptr;
+    mDrawingState.acquireFence = sp<Fence>::make(-1);
+    mDrawingState.acquireFenceTime = std::make_unique<FenceTime>(mDrawingState.acquireFence);
+    mCallbackHandleAcquireTimeOrFence = mDrawingState.acquireFenceTime->getSignalTime();
+    mDrawingState.releaseBufferEndpoint = nullptr;
+}
+
 bool Layer::setBuffer(std::shared_ptr<renderengine::ExternalTexture>& buffer,
                       const BufferData& bufferData, nsecs_t postTime, nsecs_t desiredPresentTime,
                       bool isAutoTimestamp, std::optional<nsecs_t> dequeueTime,
                       const FrameTimelineInfo& info) {
     ATRACE_FORMAT("setBuffer %s - hasBuffer=%s", getDebugName(), (buffer ? "true" : "false"));
-    if (!buffer) {
-        return false;
-    }
 
     const bool frameNumberChanged =
             bufferData.flags.test(BufferData::BufferDataChange::frameNumberChanged);
@@ -3053,17 +3076,35 @@
                                       mLastClientCompositionFence);
             mLastClientCompositionFence = nullptr;
         }
-    } else {
+    } else if (buffer) {
         // 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.desiredPresentTime = desiredPresentTime;
+    mDrawingState.isAutoTimestamp = isAutoTimestamp;
+    mDrawingState.latchedVsyncId = info.vsyncId;
+    mDrawingState.useVsyncIdForRefreshRateSelection = info.useForRefreshRateSelection;
+    mDrawingState.modified = true;
+    if (!buffer) {
+        resetDrawingStateBufferInfo();
+        setTransactionFlags(eTransactionNeeded);
+        mDrawingState.bufferSurfaceFrameTX = nullptr;
+        setFrameTimelineVsyncForBufferlessTransaction(info, postTime);
+        return true;
+    }
+
     mDrawingState.producerId = bufferData.producerId;
+    mDrawingState.barrierProducerId =
+            std::max(mDrawingState.producerId, mDrawingState.barrierProducerId);
     mDrawingState.frameNumber = frameNumber;
+    mDrawingState.barrierFrameNumber =
+            std::max(mDrawingState.frameNumber, mDrawingState.barrierFrameNumber);
+
+    // TODO(b/277265947) log and flush transaction trace when we detect out of order updates
     mDrawingState.releaseBufferListener = bufferData.releaseBufferListener;
     mDrawingState.buffer = std::move(buffer);
-    mDrawingState.clientCacheId = bufferData.cachedBuffer;
     mDrawingState.acquireFence = bufferData.flags.test(BufferData::BufferDataChange::fenceChanged)
             ? bufferData.acquireFence
             : Fence::NO_FENCE;
@@ -3076,15 +3117,11 @@
     } else {
         mCallbackHandleAcquireTimeOrFence = mDrawingState.acquireFenceTime->getSignalTime();
     }
-    mDrawingState.latchedVsyncId = info.vsyncId;
-    mDrawingState.modified = true;
     setTransactionFlags(eTransactionNeeded);
 
     const int32_t layerId = getSequence();
     mFlinger->mTimeStats->setPostTime(layerId, mDrawingState.frameNumber, getName().c_str(),
                                       mOwnerUid, postTime, getGameMode());
-    mDrawingState.desiredPresentTime = desiredPresentTime;
-    mDrawingState.isAutoTimestamp = isAutoTimestamp;
 
     if (mFlinger->mLegacyFrontEndEnabled) {
         recordLayerHistoryBufferUpdate(getLayerProps());
@@ -3111,15 +3148,31 @@
 }
 
 void Layer::recordLayerHistoryBufferUpdate(const scheduler::LayerProps& layerProps) {
+    ATRACE_CALL();
     const nsecs_t presentTime = [&] {
-        if (!mDrawingState.isAutoTimestamp) return mDrawingState.desiredPresentTime;
+        if (!mDrawingState.isAutoTimestamp) {
+            ATRACE_FORMAT_INSTANT("desiredPresentTime");
+            return mDrawingState.desiredPresentTime;
+        }
 
-        const auto prediction = mFlinger->mFrameTimeline->getTokenManager()->getPredictionsForToken(
-                mDrawingState.latchedVsyncId);
-        if (prediction.has_value()) return prediction->presentTime;
+        if (mDrawingState.useVsyncIdForRefreshRateSelection) {
+            const auto prediction =
+                    mFlinger->mFrameTimeline->getTokenManager()->getPredictionsForToken(
+                            mDrawingState.latchedVsyncId);
+            if (prediction.has_value()) {
+                ATRACE_FORMAT_INSTANT("predictedPresentTime");
+                return prediction->presentTime;
+            }
+        }
 
         return static_cast<nsecs_t>(0);
     }();
+
+    if (ATRACE_ENABLED() && presentTime > 0) {
+        const auto presentIn = TimePoint::fromNs(presentTime) - TimePoint::now();
+        ATRACE_FORMAT_INSTANT("presentIn %s", to_string(presentIn).c_str());
+    }
+
     mFlinger->mScheduler->recordLayerHistory(sequence, layerProps, presentTime,
                                              scheduler::LayerHistory::LayerUpdateType::Buffer);
 }
@@ -3132,7 +3185,6 @@
 }
 
 bool Layer::setDataspace(ui::Dataspace dataspace) {
-    mDrawingState.dataspaceRequested = true;
     if (mDrawingState.dataspace == dataspace) return false;
     mDrawingState.dataspace = dataspace;
     mDrawingState.modified = true;
@@ -3141,11 +3193,11 @@
 }
 
 bool Layer::setExtendedRangeBrightness(float currentBufferRatio, float desiredRatio) {
-    if (mDrawingState.currentSdrHdrRatio == currentBufferRatio &&
-        mDrawingState.desiredSdrHdrRatio == desiredRatio)
+    if (mDrawingState.currentHdrSdrRatio == currentBufferRatio &&
+        mDrawingState.desiredHdrSdrRatio == desiredRatio)
         return false;
-    mDrawingState.currentSdrHdrRatio = currentBufferRatio;
-    mDrawingState.desiredSdrHdrRatio = desiredRatio;
+    mDrawingState.currentHdrSdrRatio = currentBufferRatio;
+    mDrawingState.desiredHdrSdrRatio = desiredRatio;
     mDrawingState.modified = true;
     setTransactionFlags(eTransactionNeeded);
     return true;
@@ -3168,6 +3220,7 @@
 }
 
 bool Layer::setSurfaceDamageRegion(const Region& surfaceDamage) {
+    if (mDrawingState.surfaceDamageRegion.hasSameRects(surfaceDamage)) return false;
     mDrawingState.surfaceDamageRegion = surfaceDamage;
     mDrawingState.modified = true;
     setTransactionFlags(eTransactionNeeded);
@@ -3259,7 +3312,7 @@
     }
 
     if (getTransformToDisplayInverse()) {
-        uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags();
+        uint32_t invTransform = SurfaceFlinger::getActiveDisplayRotationFlags();
         if (invTransform & ui::Transform::ROT_90) {
             std::swap(bufWidth, bufHeight);
         }
@@ -3332,7 +3385,7 @@
     const State& s(getDrawingState());
 
     if (!s.buffer) {
-        if (bgColorOnly) {
+        if (bgColorOnly || mBufferInfo.mBuffer) {
             for (auto& handle : mDrawingState.callbackHandles) {
                 handle->latchTime = latchTime;
             }
@@ -3379,12 +3432,19 @@
 }
 
 void Layer::gatherBufferInfo() {
-    if (!mBufferInfo.mBuffer || !mDrawingState.buffer->hasSameBuffer(*mBufferInfo.mBuffer)) {
+    mPreviousReleaseCallbackId = {getCurrentBufferId(), mBufferInfo.mFrameNumber};
+    mPreviousReleaseBufferEndpoint = mBufferInfo.mReleaseBufferEndpoint;
+    if (!mDrawingState.buffer) {
+        mBufferInfo = {};
+        return;
+    }
+
+    if ((!mBufferInfo.mBuffer || !mDrawingState.buffer->hasSameBuffer(*mBufferInfo.mBuffer))) {
         decrementPendingBufferCount();
     }
 
-    mPreviousReleaseCallbackId = {getCurrentBufferId(), mBufferInfo.mFrameNumber};
     mBufferInfo.mBuffer = mDrawingState.buffer;
+    mBufferInfo.mReleaseBufferEndpoint = mDrawingState.releaseBufferEndpoint;
     mBufferInfo.mFence = mDrawingState.acquireFence;
     mBufferInfo.mFrameNumber = mDrawingState.frameNumber;
     mBufferInfo.mPixelFormat =
@@ -3396,11 +3456,47 @@
     mBufferInfo.mTransform = mDrawingState.bufferTransform;
     auto lastDataspace = mBufferInfo.mDataspace;
     mBufferInfo.mDataspace = translateDataspace(mDrawingState.dataspace);
+    if (mBufferInfo.mBuffer != nullptr) {
+        auto& mapper = GraphicBufferMapper::get();
+        // TODO: We should measure if it's faster to do a blind write if we're on newer api levels
+        // and don't need to possibly remaps buffers.
+        ui::Dataspace dataspace = ui::Dataspace::UNKNOWN;
+        status_t err = OK;
+        {
+            ATRACE_NAME("getDataspace");
+            err = mapper.getDataspace(mBufferInfo.mBuffer->getBuffer()->handle, &dataspace);
+        }
+        if (err != OK || dataspace != mBufferInfo.mDataspace) {
+            {
+                ATRACE_NAME("setDataspace");
+                err = mapper.setDataspace(mBufferInfo.mBuffer->getBuffer()->handle,
+                                          static_cast<ui::Dataspace>(mBufferInfo.mDataspace));
+            }
+
+            // Some GPU drivers may cache gralloc metadata which means before we composite we need
+            // to upsert RenderEngine's caches. Put in a special workaround to be backwards
+            // compatible with old vendors, with a ticking clock.
+            static const int32_t kVendorVersion =
+                    base::GetIntProperty("ro.vndk.version", __ANDROID_API_FUTURE__);
+            if (const auto format =
+                        static_cast<aidl::android::hardware::graphics::common::PixelFormat>(
+                                mBufferInfo.mBuffer->getPixelFormat());
+                err == OK && kVendorVersion < __ANDROID_API_U__ &&
+                (format ==
+                         aidl::android::hardware::graphics::common::PixelFormat::
+                                 IMPLEMENTATION_DEFINED ||
+                 format == aidl::android::hardware::graphics::common::PixelFormat::YCBCR_420_888 ||
+                 format == aidl::android::hardware::graphics::common::PixelFormat::YV12 ||
+                 format == aidl::android::hardware::graphics::common::PixelFormat::YCBCR_P010)) {
+                mBufferInfo.mBuffer->remapBuffer();
+            }
+        }
+    }
     if (lastDataspace != mBufferInfo.mDataspace) {
         mFlinger->mHdrLayerInfoChanged = true;
     }
-    if (mBufferInfo.mDesiredSdrHdrRatio != mDrawingState.desiredSdrHdrRatio) {
-        mBufferInfo.mDesiredSdrHdrRatio = mDrawingState.desiredSdrHdrRatio;
+    if (mBufferInfo.mDesiredHdrSdrRatio != mDrawingState.desiredHdrSdrRatio) {
+        mBufferInfo.mDesiredHdrSdrRatio = mDrawingState.desiredHdrSdrRatio;
         mFlinger->mHdrLayerInfoChanged = true;
     }
     mBufferInfo.mCrop = computeBufferCrop(mDrawingState);
@@ -3423,11 +3519,11 @@
     }
 }
 
-sp<Layer> Layer::createClone() {
+sp<Layer> Layer::createClone(uint32_t mirrorRootId) {
     LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0, LayerMetadata());
     args.textureName = mTextureName;
     sp<Layer> layer = mFlinger->getFactory().createBufferStateLayer(args);
-    layer->setInitialValuesForClone(sp<Layer>::fromExisting(this));
+    layer->setInitialValuesForClone(sp<Layer>::fromExisting(this), mirrorRootId);
     return layer;
 }
 
@@ -3461,20 +3557,46 @@
 }
 
 /**
+ * Returns the bounds used to fill the input frame and the touchable region.
+ *
  * Similar to getInputTransform, we need to update the bounds to include the transform.
  * This is because bounds don't include the buffer transform, where the input assumes
  * that's already included.
  */
-Rect Layer::getInputBounds() const {
-    if (!hasBufferOrSidebandStream()) {
-        return getCroppedBufferSize(getDrawingState());
+std::pair<FloatRect, bool> Layer::getInputBounds(bool fillParentBounds) const {
+    Rect croppedBufferSize = getCroppedBufferSize(getDrawingState());
+    FloatRect inputBounds = croppedBufferSize.toFloatRect();
+    if (hasBufferOrSidebandStream() && croppedBufferSize.isValid() &&
+        mDrawingState.transform.getType() != ui::Transform::IDENTITY) {
+        inputBounds = mDrawingState.transform.transform(inputBounds);
     }
 
-    Rect bufferBounds = getCroppedBufferSize(getDrawingState());
-    if (mDrawingState.transform.getType() == ui::Transform::IDENTITY || !bufferBounds.isValid()) {
-        return bufferBounds;
+    bool inputBoundsValid = croppedBufferSize.isValid();
+    if (!inputBoundsValid) {
+        /**
+         * Input bounds are based on the layer crop or buffer size. But if we are using
+         * the layer bounds as the input bounds (replaceTouchableRegionWithCrop flag) then
+         * we can use the parent bounds as the input bounds if the layer does not have buffer
+         * or a crop. We want to unify this logic but because of compat reasons we cannot always
+         * use the parent bounds. A layer without a buffer can get input. So when a window is
+         * initially added, its touchable region can fill its parent layer bounds and that can
+         * have negative consequences.
+         */
+        inputBounds = fillParentBounds ? mBounds : FloatRect{};
     }
-    return mDrawingState.transform.transform(bufferBounds);
+
+    // Clamp surface inset to the input bounds.
+    const float inset = static_cast<float>(mDrawingState.inputInfo.surfaceInset);
+    const float xSurfaceInset = std::clamp(inset, 0.f, inputBounds.getWidth() / 2.f);
+    const float ySurfaceInset = std::clamp(inset, 0.f, inputBounds.getHeight() / 2.f);
+
+    // Apply the insets to the input bounds.
+    inputBounds.left += xSurfaceInset;
+    inputBounds.top += ySurfaceInset;
+    inputBounds.right -= xSurfaceInset;
+    inputBounds.bottom -= ySurfaceInset;
+
+    return {inputBounds, inputBoundsValid};
 }
 
 bool Layer::simpleBufferUpdate(const layer_state_t& s) const {
@@ -3660,9 +3782,9 @@
     }
 
     if (s.what & layer_state_t::eExtendedRangeBrightnessChanged) {
-        if (mDrawingState.currentSdrHdrRatio != s.currentSdrHdrRatio ||
-            mDrawingState.desiredSdrHdrRatio != s.desiredSdrHdrRatio) {
-            ALOGV("%s: false [eDimmingEnabledChanged changed]", __func__);
+        if (mDrawingState.currentHdrSdrRatio != s.currentHdrSdrRatio ||
+            mDrawingState.desiredHdrSdrRatio != s.desiredHdrSdrRatio) {
+            ALOGV("%s: false [eExtendedRangeBrightnessChanged changed]", __func__);
             return false;
         }
     }
@@ -3852,6 +3974,10 @@
     mBufferInfo.mFrameLatencyNeeded = false;
 }
 
+bool Layer::willReleaseBufferOnLatch() const {
+    return !mDrawingState.buffer && mBufferInfo.mBuffer;
+}
+
 bool Layer::latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime) {
     const bool bgColorOnly = mDrawingState.bgColorLayer != nullptr;
     return latchBufferImpl(recomputeVisibleRegions, latchTime, bgColorOnly);
@@ -3875,9 +4001,6 @@
         return false;
     }
     updateTexImage(latchTime, bgColorOnly);
-    if (mDrawingState.buffer == nullptr) {
-        return false;
-    }
 
     // Capture the old state of the layer for comparisons later
     BufferInfo oldBufferInfo = mBufferInfo;
@@ -3886,6 +4009,18 @@
     mCurrentFrameNumber = mDrawingState.frameNumber;
     gatherBufferInfo();
 
+    if (mBufferInfo.mBuffer) {
+        // We latched a buffer that will be presented soon. Clear the previously presented layer
+        // stack list.
+        mPreviouslyPresentedLayerStacks.clear();
+    }
+
+    if (mDrawingState.buffer == nullptr) {
+        const bool bufferReleased = oldBufferInfo.mBuffer != nullptr;
+        recomputeVisibleRegions = bufferReleased;
+        return bufferReleased;
+    }
+
     if (oldBufferInfo.mBuffer == nullptr) {
         // the first time we receive a buffer, we need to trigger a
         // geometry invalidation.
@@ -3960,10 +4095,6 @@
 }
 
 ui::Dataspace Layer::getDataSpace() const {
-    return mDrawingState.dataspaceRequested ? getRequestedDataSpace() : ui::Dataspace::UNKNOWN;
-}
-
-ui::Dataspace Layer::getRequestedDataSpace() const {
     return hasBufferOrSidebandStream() ? mBufferInfo.mDataspace : mDrawingState.dataspace;
 }
 
@@ -3971,6 +4102,8 @@
     ui::Dataspace updatedDataspace = dataspace;
     // translate legacy dataspaces to modern dataspaces
     switch (dataspace) {
+        // Treat unknown dataspaces as V0_sRGB
+        case ui::Dataspace::UNKNOWN:
         case ui::Dataspace::SRGB:
             updatedDataspace = ui::Dataspace::V0_SRGB;
             break;
@@ -4000,10 +4133,10 @@
     return mBufferInfo.mBuffer ? mBufferInfo.mBuffer->getBuffer() : nullptr;
 }
 
-void Layer::setTransformHint(ui::Transform::RotationFlags displayTransformHint) {
-    mTransformHint = getFixedTransformHint();
-    if (mTransformHint == ui::Transform::ROT_INVALID) {
-        mTransformHint = displayTransformHint;
+void Layer::setTransformHintLegacy(ui::Transform::RotationFlags displayTransformHint) {
+    mTransformHintLegacy = getFixedTransformHint();
+    if (mTransformHintLegacy == ui::Transform::ROT_INVALID) {
+        mTransformHintLegacy = displayTransformHint;
     }
     mSkipReportingTransformHint = false;
 }
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 2fb122c..38590e6 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -137,17 +137,22 @@
         wp<Layer> touchableRegionCrop;
 
         ui::Dataspace dataspace;
-        bool dataspaceRequested;
 
         uint64_t frameNumber;
+        // high watermark framenumber to use to check for barriers to protect ourselves
+        // from out of order transactions
+        uint64_t barrierFrameNumber;
         ui::Transform transform;
 
         uint32_t producerId = 0;
+        // high watermark producerId to use to check for barriers to protect ourselves
+        // from out of order transactions
+        uint32_t barrierProducerId = 0;
+
         uint32_t bufferTransform;
         bool transformToDisplayInverse;
         Region transparentRegionHint;
         std::shared_ptr<renderengine::ExternalTexture> buffer;
-        client_cache_t clientCacheId;
         sp<Fence> acquireFence;
         std::shared_ptr<FenceTime> acquireFenceTime;
         HdrMetadata hdrMetadata;
@@ -225,10 +230,11 @@
         gui::DropInputMode dropInputMode;
         bool autoRefresh = false;
         bool dimmingEnabled = true;
-        float currentSdrHdrRatio = 1.f;
-        float desiredSdrHdrRatio = 1.f;
+        float currentHdrSdrRatio = 1.f;
+        float desiredHdrSdrRatio = 1.f;
         gui::CachingHint cachingHint = gui::CachingHint::Enabled;
         int64_t latchedVsyncId = 0;
+        bool useVsyncIdForRefreshRateSelection = false;
     };
 
     explicit Layer(const LayerCreationArgs& args);
@@ -243,7 +249,7 @@
     // true if this layer is visible, false otherwise
     virtual bool isVisible() const;
 
-    virtual sp<Layer> createClone();
+    virtual sp<Layer> createClone(uint32_t mirrorRoot);
 
     // Set a 2x2 transformation matrix on the layer. This transform
     // will be applied after parent transforms, but before any final
@@ -290,14 +296,14 @@
 
     virtual bool setMetadata(const LayerMetadata& data);
     virtual void setChildrenDrawingParent(const sp<Layer>&);
-    virtual bool reparent(const sp<IBinder>& newParentHandle);
+    virtual bool reparent(const sp<IBinder>& newParentHandle) REQUIRES(mFlinger->mStateLock);
     virtual bool setColorTransform(const mat4& matrix);
     virtual mat4 getColorTransform() const;
     virtual bool hasColorTransform() const;
     virtual bool isColorSpaceAgnostic() const { return mDrawingState.colorSpaceAgnostic; }
     virtual bool isDimmingEnabled() const { return getDrawingState().dimmingEnabled; }
-    float getDesiredSdrHdrRatio() const { return getDrawingState().desiredSdrHdrRatio; }
-    float getCurrentSdrHdrRatio() const { return getDrawingState().currentSdrHdrRatio; }
+    float getDesiredHdrSdrRatio() const { return getDrawingState().desiredHdrSdrRatio; }
+    float getCurrentHdrSdrRatio() const { return getDrawingState().currentHdrSdrRatio; }
     gui::CachingHint getCachingHint() const { return getDrawingState().cachingHint; }
 
     bool setTransform(uint32_t /*transform*/);
@@ -316,7 +322,8 @@
     bool setSidebandStream(const sp<NativeHandle>& /*sidebandStream*/);
     bool setTransactionCompletedListeners(const std::vector<sp<CallbackHandle>>& /*handles*/,
                                           bool willPresent);
-    virtual bool setBackgroundColor(const half3& color, float alpha, ui::Dataspace dataspace);
+    virtual bool setBackgroundColor(const half3& color, float alpha, ui::Dataspace dataspace)
+            REQUIRES(mFlinger->mStateLock);
     virtual bool setColorSpaceAgnostic(const bool agnostic);
     virtual bool setDimmingEnabled(const bool dimmingEnabled);
     virtual bool setDefaultFrameRateCompatibility(FrameRateCompatibility compatibility);
@@ -332,7 +339,6 @@
     virtual FrameRateCompatibility getDefaultFrameRateCompatibility() const;
     //
     ui::Dataspace getDataSpace() const;
-    ui::Dataspace getRequestedDataSpace() const;
 
     virtual sp<LayerFE> getCompositionEngineLayerFE() const;
     virtual sp<LayerFE> copyCompositionEngineLayerFE() const;
@@ -366,6 +372,21 @@
     bool canReceiveInput() const;
 
     /*
+     * Whether or not the layer should be considered visible for input calculations.
+     */
+    virtual bool isVisibleForInput() const {
+        // For compatibility reasons we let layers which can receive input
+        // receive input before they have actually submitted a buffer. Because
+        // of this we use canReceiveInput instead of isVisible to check the
+        // policy-visibility, ignoring the buffer state. However for layers with
+        // hasInputInfo()==false we can use the real visibility state.
+        // We are just using these layers for occlusion detection in
+        // InputDispatcher, and obviously if they aren't visible they can't occlude
+        // anything.
+        return hasInputInfo() ? canReceiveInput() : isVisible();
+    }
+
+    /*
      * isProtected - true if the layer may contain protected contents in the
      * GRALLOC_USAGE_PROTECTED sense.
      */
@@ -434,6 +455,12 @@
                          bool bgColorOnly);
 
     /*
+     * Returns true if the currently presented buffer will be released when this layer state
+     * is latched. This will return false if there is no buffer currently presented.
+     */
+    bool willReleaseBufferOnLatch() const;
+
+    /*
      * Calls latchBuffer if the buffer has a frame queued and then releases the buffer.
      * This is used if the buffer is just latched and releases to free up the buffer
      * and will not be shown on screen.
@@ -455,8 +482,6 @@
     sp<GraphicBuffer> getBuffer() const;
     const std::shared_ptr<renderengine::ExternalTexture>& getExternalTexture() const;
 
-    ui::Transform::RotationFlags getTransformHint() const { return mTransformHint; }
-
     /*
      * Returns if a frame is ready
      */
@@ -517,9 +542,10 @@
 
         std::shared_ptr<renderengine::ExternalTexture> mBuffer;
         uint64_t mFrameNumber;
+        sp<IBinder> mReleaseBufferEndpoint;
 
         bool mFrameLatencyNeeded{false};
-        float mDesiredSdrHdrRatio = 1.f;
+        float mDesiredHdrSdrRatio = 1.f;
     };
 
     BufferInfo mBufferInfo;
@@ -528,7 +554,7 @@
     const compositionengine::LayerFECompositionState* getCompositionState() const;
     bool fenceHasSignaled() const;
     void onPreComposition(nsecs_t refreshStartTime);
-    void onLayerDisplayed(ftl::SharedFuture<FenceResult>);
+    void onLayerDisplayed(ftl::SharedFuture<FenceResult>, ui::LayerStack layerStack);
 
     void setWasClientComposed(const sp<Fence>& fence) {
         mLastClientCompositionFence = fence;
@@ -570,6 +596,8 @@
 
     FloatRect getBounds(const Region& activeTransparentRegion) const;
     FloatRect getBounds() const;
+    Rect getInputBoundsInDisplaySpace(const FloatRect& insetBounds,
+                                      const ui::Transform& displayTransform);
 
     // Compute bounds for the layer and cache the results.
     void computeBounds(FloatRect parentBounds, ui::Transform parentTransform, float shadowRadius);
@@ -610,7 +638,7 @@
     bool isRemovedFromCurrentState() const;
 
     LayerProto* writeToProto(LayersProto& layersProto, uint32_t traceFlags);
-    void writeCompositionStateToProto(LayerProto* layerProto);
+    void writeCompositionStateToProto(LayerProto* layerProto, ui::LayerStack layerStack);
 
     // Write states that are modified by the main thread. This includes drawing
     // state as well as buffer data. This should be called in the main or tracing
@@ -624,7 +652,7 @@
 
     gui::WindowInfo::Type getWindowType() const { return mWindowType; }
 
-    void updateMirrorInfo();
+    bool updateMirrorInfo(const std::deque<Layer*>& cloneRootsPendingUpdates);
 
     /*
      * doTransaction - process the transaction. This is a good place to figure
@@ -641,13 +669,13 @@
     /*
      * Remove from current state and mark for removal.
      */
-    void removeFromCurrentState();
+    void removeFromCurrentState() REQUIRES(mFlinger->mStateLock);
 
     /*
      * called with the state lock from a binder thread when the layer is
      * removed from the current list to the pending removal list
      */
-    void onRemovedFromCurrentState();
+    void onRemovedFromCurrentState() REQUIRES(mFlinger->mStateLock);
 
     /*
      * Called when the layer is added back to the current state list.
@@ -700,6 +728,7 @@
     void traverse(LayerVector::StateSet, const LayerVector::Visitor&);
     void traverseInReverseZOrder(LayerVector::StateSet, const LayerVector::Visitor&);
     void traverseInZOrder(LayerVector::StateSet, const LayerVector::Visitor&);
+    void traverseChildren(const LayerVector::Visitor&);
 
     /**
      * Traverse only children in z order, ignoring relative layers that are not children of the
@@ -707,7 +736,10 @@
      */
     void traverseChildrenInZOrder(LayerVector::StateSet, const LayerVector::Visitor&);
 
-    size_t getChildrenCount() const;
+    size_t getDescendantCount() const;
+    size_t getChildrenCount() const { return mDrawingChildren.size(); }
+    bool isHandleAlive() const { return mHandleAlive; }
+    bool onHandleDestroyed() { return mHandleAlive = false; }
 
     // ONLY CALL THIS FROM THE LAYER DTOR!
     // See b/141111965.  We need to add current children to offscreen layers in
@@ -848,6 +880,11 @@
     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,
@@ -867,6 +904,15 @@
         };
     };
     bool hasBuffer() const { return mBufferInfo.mBuffer != nullptr; }
+    void setTransformHint(std::optional<ui::Transform::RotationFlags> transformHint) {
+        mTransformHint = transformHint;
+    }
+    // Keeps track of the previously presented layer stacks. This is used to get
+    // the release fences from the correct displays when we release the last buffer
+    // from the layer.
+    std::vector<ui::LayerStack> mPreviouslyPresentedLayerStacks;
+    // Exposed so SurfaceFlinger can assert that it's held
+    const sp<SurfaceFlinger> mFlinger;
 
 protected:
     // For unit tests
@@ -877,7 +923,7 @@
     friend class TransactionFrameTracerTest;
     friend class TransactionSurfaceFrameTest;
 
-    virtual void setInitialValuesForClone(const sp<Layer>& clonedFrom);
+    virtual void setInitialValuesForClone(const sp<Layer>& clonedFrom, uint32_t mirrorRootId);
     void preparePerFrameCompositionState();
     void preparePerFrameBufferCompositionState();
     void preparePerFrameEffectsCompositionState();
@@ -885,10 +931,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);
@@ -931,10 +973,7 @@
      * "replaceTouchableRegionWithCrop" is specified. In this case, the layer will receive input
      * in this layer's space, regardless of the specified crop layer.
      */
-    Rect getInputBounds() const;
-
-    // constant
-    sp<SurfaceFlinger> mFlinger;
+    std::pair<FloatRect, bool> getInputBounds(bool fillParentBounds) const;
 
     bool mPremultipliedAlpha{true};
     const std::string mName;
@@ -1147,16 +1186,19 @@
     float mBorderWidth;
     half4 mBorderColor;
 
-    void setTransformHint(ui::Transform::RotationFlags);
+    void setTransformHintLegacy(ui::Transform::RotationFlags);
+    void resetDrawingStateBufferInfo();
 
     const uint32_t mTextureName;
 
     // Transform hint provided to the producer. This must be accessed holding
     // the mStateLock.
-    ui::Transform::RotationFlags mTransformHint = ui::Transform::ROT_0;
+    ui::Transform::RotationFlags mTransformHintLegacy = ui::Transform::ROT_0;
     bool mSkipReportingTransformHint = true;
+    std::optional<ui::Transform::RotationFlags> mTransformHint = std::nullopt;
 
     ReleaseCallbackId mPreviousReleaseCallbackId = ReleaseCallbackId::INVALID_ID;
+    sp<IBinder> mPreviousReleaseBufferEndpoint;
     uint64_t mPreviousReleasedFrameNumber = 0;
 
     uint64_t mPreviousBarrierFrameNumber = 0;
@@ -1191,6 +1233,7 @@
     std::vector<std::pair<frontend::LayerHierarchy::TraversalPath, sp<LayerFE>>> mLayerFEs;
     std::unique_ptr<frontend::LayerSnapshot> mSnapshot =
             std::make_unique<frontend::LayerSnapshot>();
+    bool mHandleAlive = false;
 };
 
 std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate);
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index b9c8b78..f855f27 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -25,8 +25,8 @@
 #include <system/window.h>
 #include <utils/Log.h>
 
-#include "DisplayDevice.h"
 #include "LayerFE.h"
+#include "SurfaceFlinger.h"
 
 namespace android {
 
@@ -260,7 +260,7 @@
          * the code below applies the primary display's inverse transform to
          * the texture transform
          */
-        uint32_t transform = DisplayDevice::getPrimaryDisplayRotationFlags();
+        uint32_t transform = SurfaceFlinger::getActiveDisplayRotationFlags();
         mat4 tr = inverseOrientation(transform);
 
         /**
@@ -325,8 +325,9 @@
     caster.shadow = state;
 }
 
-void LayerFE::onLayerDisplayed(ftl::SharedFuture<FenceResult> futureFenceResult) {
-    mCompositionResult.releaseFences.emplace_back(std::move(futureFenceResult));
+void LayerFE::onLayerDisplayed(ftl::SharedFuture<FenceResult> futureFenceResult,
+                               ui::LayerStack layerStack) {
+    mCompositionResult.releaseFences.emplace_back(std::move(futureFenceResult), layerStack);
 }
 
 CompositionResult&& LayerFE::stealCompositionResult() {
diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h
index c23bd31..d584fb7 100644
--- a/services/surfaceflinger/LayerFE.h
+++ b/services/surfaceflinger/LayerFE.h
@@ -29,7 +29,7 @@
     // TODO(b/238781169) update CE to no longer pass refreshStartTime to LayerFE::onPreComposition
     // and remove this field.
     nsecs_t refreshStartTime = 0;
-    std::vector<ftl::SharedFuture<FenceResult>> releaseFences;
+    std::vector<std::pair<ftl::SharedFuture<FenceResult>, ui::LayerStack>> releaseFences;
     sp<Fence> lastClientCompositionFence = nullptr;
 };
 
@@ -40,7 +40,7 @@
     // compositionengine::LayerFE overrides
     const compositionengine::LayerFECompositionState* getCompositionState() const override;
     bool onPreComposition(nsecs_t refreshStartTime, bool updatingOutputGeometryThisFrame) override;
-    void onLayerDisplayed(ftl::SharedFuture<FenceResult>) override;
+    void onLayerDisplayed(ftl::SharedFuture<FenceResult>, ui::LayerStack) override;
     const char* getDebugName() const override;
     int32_t getSequence() const override;
     bool hasRoundedCorners() const override;
diff --git a/services/surfaceflinger/LayerProtoHelper.cpp b/services/surfaceflinger/LayerProtoHelper.cpp
index 55281fa..3472d20 100644
--- a/services/surfaceflinger/LayerProtoHelper.cpp
+++ b/services/surfaceflinger/LayerProtoHelper.cpp
@@ -15,6 +15,8 @@
  */
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
+#include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/LayerSnapshot.h"
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
 #pragma clang diagnostic ignored "-Wextra"
@@ -248,48 +250,89 @@
     outRegion.bottom = proto.bottom();
 }
 
-void LayerProtoHelper::writeHierarchyToProto(
-        LayersProto& outLayersProto, const frontend::LayerHierarchy& root,
-        const frontend::LayerSnapshotBuilder& snapshotBuilder,
-        const std::unordered_map<uint32_t, sp<Layer>>& legacyLayers, uint32_t traceFlags) {
-    using Variant = frontend::LayerHierarchy::Variant;
-    frontend::LayerSnapshot defaultSnapshot;
-
-    LayerProto* layerProto = outLayersProto.add_layers();
-    const frontend::RequestedLayerState& layer = *root.getLayer();
-    frontend::LayerSnapshot* snapshot = snapshotBuilder.getSnapshot(layer.id);
-
-    if (!snapshot) {
-        defaultSnapshot.uniqueSequence = layer.id;
-        snapshot = &defaultSnapshot;
-    }
-    writeSnapshotToProto(layerProto, layer, *snapshot, traceFlags);
-    for (const auto& [child, variant] : root.mChildren) {
-        if (variant == Variant::Attached || variant == Variant::Detached) {
-            layerProto->add_children(child->getLayer()->id);
-        } else if (variant == Variant::Relative) {
-            layerProto->add_relatives(child->getLayer()->id);
+LayersProto LayerProtoFromSnapshotGenerator::generate(const frontend::LayerHierarchy& root) {
+    mLayersProto.clear_layers();
+    std::unordered_set<uint64_t> stackIdsToSkip;
+    if ((mTraceFlags & LayerTracing::TRACE_VIRTUAL_DISPLAYS) == 0) {
+        for (const auto& [layerStack, displayInfo] : mDisplayInfos) {
+            if (displayInfo.isVirtual) {
+                stackIdsToSkip.insert(layerStack.id);
+            }
         }
     }
 
-    auto parent = root.getParent();
-    if (parent && parent->getLayer()) {
-        layerProto->set_parent(parent->getLayer()->id);
-    } else {
-        layerProto->set_parent(-1);
+    frontend::LayerHierarchy::TraversalPath path = frontend::LayerHierarchy::TraversalPath::ROOT;
+    for (auto& [child, variant] : root.mChildren) {
+        if (variant != frontend::LayerHierarchy::Variant::Attached ||
+            stackIdsToSkip.find(child->getLayer()->layerStack.id) != stackIdsToSkip.end()) {
+            continue;
+        }
+        frontend::LayerHierarchy::ScopedAddToTraversalPath addChildToPath(path,
+                                                                          child->getLayer()->id,
+                                                                          variant);
+        LayerProtoFromSnapshotGenerator::writeHierarchyToProto(*child, path);
     }
 
-    auto relativeParent = root.getRelativeParent();
-    if (relativeParent && relativeParent->getLayer()) {
-        layerProto->set_z_order_relative_of(relativeParent->getLayer()->id);
-    } else {
-        layerProto->set_z_order_relative_of(-1);
+    // fill in relative and parent info
+    for (int i = 0; i < mLayersProto.layers_size(); i++) {
+        auto layerProto = mLayersProto.mutable_layers()->Mutable(i);
+        auto it = mChildToRelativeParent.find(layerProto->id());
+        if (it == mChildToRelativeParent.end()) {
+            layerProto->set_z_order_relative_of(-1);
+        } else {
+            layerProto->set_z_order_relative_of(it->second);
+        }
+        it = mChildToParent.find(layerProto->id());
+        if (it == mChildToParent.end()) {
+            layerProto->set_parent(-1);
+        } else {
+            layerProto->set_parent(it->second);
+        }
     }
 
-    if (traceFlags & LayerTracing::TRACE_COMPOSITION) {
-        auto it = legacyLayers.find(layer.id);
-        if (it != legacyLayers.end()) {
-            it->second->writeCompositionStateToProto(layerProto);
+    mDefaultSnapshots.clear();
+    mChildToRelativeParent.clear();
+    return std::move(mLayersProto);
+}
+
+frontend::LayerSnapshot* LayerProtoFromSnapshotGenerator::getSnapshot(
+        frontend::LayerHierarchy::TraversalPath& path, const frontend::RequestedLayerState& layer) {
+    frontend::LayerSnapshot* snapshot = mSnapshotBuilder.getSnapshot(path);
+    if (snapshot) {
+        return snapshot;
+    } else {
+        mDefaultSnapshots[path] = frontend::LayerSnapshot(layer, path);
+        return &mDefaultSnapshots[path];
+    }
+}
+
+void LayerProtoFromSnapshotGenerator::writeHierarchyToProto(
+        const frontend::LayerHierarchy& root, frontend::LayerHierarchy::TraversalPath& path) {
+    using Variant = frontend::LayerHierarchy::Variant;
+    LayerProto* layerProto = mLayersProto.add_layers();
+    const frontend::RequestedLayerState& layer = *root.getLayer();
+    frontend::LayerSnapshot* snapshot = getSnapshot(path, layer);
+    LayerProtoHelper::writeSnapshotToProto(layerProto, layer, *snapshot, mTraceFlags);
+
+    for (const auto& [child, variant] : root.mChildren) {
+        frontend::LayerHierarchy::ScopedAddToTraversalPath addChildToPath(path,
+                                                                          child->getLayer()->id,
+                                                                          variant);
+        frontend::LayerSnapshot* childSnapshot = getSnapshot(path, layer);
+        if (variant == Variant::Attached || variant == Variant::Detached ||
+            variant == Variant::Mirror) {
+            mChildToParent[childSnapshot->uniqueSequence] = snapshot->uniqueSequence;
+            layerProto->add_children(childSnapshot->uniqueSequence);
+        } else if (variant == Variant::Relative) {
+            mChildToRelativeParent[childSnapshot->uniqueSequence] = snapshot->uniqueSequence;
+            layerProto->add_relatives(childSnapshot->uniqueSequence);
+        }
+    }
+
+    if (mTraceFlags & LayerTracing::TRACE_COMPOSITION) {
+        auto it = mLegacyLayers.find(layer.id);
+        if (it != mLegacyLayers.end()) {
+            it->second->writeCompositionStateToProto(layerProto, snapshot->outputFilter.layerStack);
         }
     }
 
@@ -298,7 +341,10 @@
         if (variant == Variant::Detached) {
             continue;
         }
-        writeHierarchyToProto(outLayersProto, *child, snapshotBuilder, legacyLayers, traceFlags);
+        frontend::LayerHierarchy::ScopedAddToTraversalPath addChildToPath(path,
+                                                                          child->getLayer()->id,
+                                                                          variant);
+        writeHierarchyToProto(*child, path);
     }
 }
 
@@ -345,7 +391,12 @@
     layerInfo->set_shadow_radius(snapshot.shadowRadius);
 
     layerInfo->set_id(snapshot.uniqueSequence);
-    layerInfo->set_name(requestedState.name);
+    layerInfo->set_original_id(snapshot.sequence);
+    if (!snapshot.path.isClone()) {
+        layerInfo->set_name(requestedState.name);
+    } else {
+        layerInfo->set_name(requestedState.name + "(Mirror)");
+    }
     layerInfo->set_type("Layer");
 
     LayerProtoHelper::writeToProto(requestedState.transparentRegion,
@@ -394,6 +445,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..b84a49b 100644
--- a/services/surfaceflinger/LayerProtoHelper.h
+++ b/services/surfaceflinger/LayerProtoHelper.h
@@ -25,6 +25,9 @@
 #include <ui/Rect.h>
 #include <ui/Region.h>
 #include <ui/Transform.h>
+#include <cstdint>
+#include "FrontEnd/LayerHierarchy.h"
+#include "FrontEnd/LayerSnapshot.h"
 
 namespace android {
 namespace surfaceflinger {
@@ -58,15 +61,44 @@
     static void readFromProto(const ColorTransformProto& colorTransformProto, mat4& matrix);
     static void writeToProto(const android::BlurRegion region, BlurRegion*);
     static void readFromProto(const BlurRegion& proto, android::BlurRegion& outRegion);
-    static void writeHierarchyToProto(LayersProto& layersProto,
-                                      const frontend::LayerHierarchy& root,
-                                      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);
+};
+
+class LayerProtoFromSnapshotGenerator {
+public:
+    LayerProtoFromSnapshotGenerator(
+            const frontend::LayerSnapshotBuilder& snapshotBuilder,
+            const display::DisplayMap<ui::LayerStack, frontend::DisplayInfo>& displayInfos,
+            const std::unordered_map<uint32_t, sp<Layer>>& legacyLayers, uint32_t traceFlags)
+          : mSnapshotBuilder(snapshotBuilder),
+            mLegacyLayers(legacyLayers),
+            mDisplayInfos(displayInfos),
+            mTraceFlags(traceFlags) {}
+    LayersProto generate(const frontend::LayerHierarchy& root);
+
+private:
+    void writeHierarchyToProto(const frontend::LayerHierarchy& root,
+                               frontend::LayerHierarchy::TraversalPath& path);
+    frontend::LayerSnapshot* getSnapshot(frontend::LayerHierarchy::TraversalPath& path,
+                                         const frontend::RequestedLayerState& layer);
+
+    const frontend::LayerSnapshotBuilder& mSnapshotBuilder;
+    const std::unordered_map<uint32_t, sp<Layer>>& mLegacyLayers;
+    const display::DisplayMap<ui::LayerStack, frontend::DisplayInfo>& mDisplayInfos;
+    uint32_t mTraceFlags;
+    LayersProto mLayersProto;
+    // winscope expects all the layers, so provide a snapshot even if it not currently drawing
+    std::unordered_map<frontend::LayerHierarchy::TraversalPath, frontend::LayerSnapshot,
+                       frontend::LayerHierarchy::TraversalPathHash>
+            mDefaultSnapshots;
+    std::unordered_map<uint32_t /* child unique seq*/, uint32_t /* relative parent unique seq*/>
+            mChildToRelativeParent;
+    std::unordered_map<uint32_t /* child unique seq*/, uint32_t /* parent unique seq*/>
+            mChildToParent;
 };
 
 } // namespace surfaceflinger
diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp
index 03a7f22..d606cff 100644
--- a/services/surfaceflinger/LayerRenderArea.cpp
+++ b/services/surfaceflinger/LayerRenderArea.cpp
@@ -39,9 +39,13 @@
 
 LayerRenderArea::LayerRenderArea(SurfaceFlinger& flinger, sp<Layer> layer, const Rect& crop,
                                  ui::Size reqSize, ui::Dataspace reqDataSpace, bool childrenOnly,
-                                 bool allowSecureLayers)
-      : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, allowSecureLayers),
+                                 bool allowSecureLayers, const ui::Transform& layerTransform,
+                                 const Rect& layerBufferSize, bool hintForSeamlessTransition)
+      : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, hintForSeamlessTransition,
+                   allowSecureLayers),
         mLayer(std::move(layer)),
+        mLayerTransform(layerTransform),
+        mLayerBufferSize(layerBufferSize),
         mCrop(crop),
         mFlinger(flinger),
         mChildrenOnly(childrenOnly) {}
@@ -60,7 +64,8 @@
 
 Rect LayerRenderArea::getSourceCrop() const {
     if (mCrop.isEmpty()) {
-        return mLayer->getBufferSize(mLayer->getDrawingState());
+        // TODO this should probably be mBounds instead of just buffer bounds
+        return mLayerBufferSize;
     } else {
         return mCrop;
     }
@@ -70,7 +75,7 @@
     using namespace std::string_literals;
 
     if (!mChildrenOnly) {
-        mTransform = mLayer->getTransform().inverse();
+        mTransform = mLayerTransform.inverse();
     }
 
     if (mFlinger.mLayerLifecycleManagerEnabled) {
@@ -80,7 +85,7 @@
     // If layer is offscreen, update mirroring info if it exists
     if (mLayer->isRemovedFromCurrentState()) {
         mLayer->traverse(LayerVector::StateSet::Drawing,
-                         [&](Layer* layer) { layer->updateMirrorInfo(); });
+                         [&](Layer* layer) { layer->updateMirrorInfo({}); });
         mLayer->traverse(LayerVector::StateSet::Drawing,
                          [&](Layer* layer) { layer->updateCloneBufferInfo(); });
     }
diff --git a/services/surfaceflinger/LayerRenderArea.h b/services/surfaceflinger/LayerRenderArea.h
index 322dbd1..aa609ee 100644
--- a/services/surfaceflinger/LayerRenderArea.h
+++ b/services/surfaceflinger/LayerRenderArea.h
@@ -33,7 +33,9 @@
 class LayerRenderArea : public RenderArea {
 public:
     LayerRenderArea(SurfaceFlinger& flinger, sp<Layer> layer, const Rect& crop, ui::Size reqSize,
-                    ui::Dataspace reqDataSpace, bool childrenOnly, bool allowSecureLayers);
+                    ui::Dataspace reqDataSpace, bool childrenOnly, bool allowSecureLayers,
+                    const ui::Transform& layerTransform, const Rect& layerBufferSize,
+                    bool hintForSeamlessTransition);
 
     const ui::Transform& getTransform() const override;
     bool isSecure() const override;
@@ -45,6 +47,8 @@
 
 private:
     const sp<Layer> mLayer;
+    const ui::Transform mLayerTransform;
+    const Rect mLayerBufferSize;
     const Rect mCrop;
 
     ui::Transform mTransform;
diff --git a/services/surfaceflinger/MutexUtils.h b/services/surfaceflinger/MutexUtils.h
index f8be6f3..58f7cb4 100644
--- a/services/surfaceflinger/MutexUtils.h
+++ b/services/surfaceflinger/MutexUtils.h
@@ -50,4 +50,14 @@
     const status_t status;
 };
 
+// Require, under penalty of compilation failure, that the compiler thinks that a mutex is held.
+#define REQUIRE_MUTEX(expr) ([]() REQUIRES(expr) {})()
+
+// Tell the compiler that we know that a mutex is held.
+#define ASSERT_MUTEX(expr) ([]() ASSERT_CAPABILITY(expr) {})()
+
+// Specify that one mutex is an alias for another.
+// (e.g. SurfaceFlinger::mStateLock and Layer::mFlinger->mStateLock)
+#define MUTEX_ALIAS(held, alias) (REQUIRE_MUTEX(held), ASSERT_MUTEX(alias))
+
 } // namespace android
diff --git a/services/surfaceflinger/OWNERS b/services/surfaceflinger/OWNERS
index 6011d0d..4e7da82 100644
--- a/services/surfaceflinger/OWNERS
+++ b/services/surfaceflinger/OWNERS
@@ -4,5 +4,7 @@
 lpy@google.com
 pdwilliams@google.com
 racarr@google.com
+ramindani@google.com
+rnlee@google.com
 scroggo@google.com
 vishnun@google.com
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index 9a4261d..f1fd6db 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -355,6 +355,8 @@
     if (isSetByHwc()) {
         transaction.setFlags(surface, layer_state_t::eLayerIsRefreshRateIndicator,
                              layer_state_t::eLayerIsRefreshRateIndicator);
+        // Disable overlay layer caching when refresh rate is updated by the HWC.
+        transaction.setCachingHint(surface, gui::CachingHint::Disabled);
     }
     transaction.setFrameRate(surface, kFrameRate, kCompatibility, kSeamlessness);
     return transaction;
diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp
index 327ca3f..8f658d5 100644
--- a/services/surfaceflinger/RegionSamplingThread.cpp
+++ b/services/surfaceflinger/RegionSamplingThread.cpp
@@ -277,10 +277,12 @@
 
     const Rect sampledBounds = sampleRegion.bounds();
     constexpr bool kUseIdentityTransform = false;
+    constexpr bool kHintForSeamlessTransition = false;
 
     SurfaceFlinger::RenderAreaFuture renderAreaFuture = ftl::defer([=] {
         return DisplayRenderArea::create(displayWeak, sampledBounds, sampledBounds.getSize(),
-                                         ui::Dataspace::V0_SRGB, kUseIdentityTransform);
+                                         ui::Dataspace::V0_SRGB, kUseIdentityTransform,
+                                         kHintForSeamlessTransition);
     });
 
     std::unordered_set<sp<IRegionSamplingListener>, SpHash<IRegionSamplingListener>> listeners;
@@ -347,7 +349,8 @@
                 }
                 visitor(layer);
             };
-            mFlinger.traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, filterVisitor);
+            mFlinger.traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, {},
+                                                filterVisitor);
         };
         getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
     }
diff --git a/services/surfaceflinger/RenderArea.h b/services/surfaceflinger/RenderArea.h
index 910fce0..71b85bd 100644
--- a/services/surfaceflinger/RenderArea.h
+++ b/services/surfaceflinger/RenderArea.h
@@ -25,12 +25,14 @@
     static float getCaptureFillValue(CaptureFill captureFill);
 
     RenderArea(ui::Size reqSize, CaptureFill captureFill, ui::Dataspace reqDataSpace,
-               bool allowSecureLayers = false, RotationFlags rotation = ui::Transform::ROT_0)
+               bool hintForSeamlessTransition, bool allowSecureLayers = false,
+               RotationFlags rotation = ui::Transform::ROT_0)
           : mAllowSecureLayers(allowSecureLayers),
             mReqSize(reqSize),
             mReqDataSpace(reqDataSpace),
             mCaptureFill(captureFill),
-            mRotationFlags(rotation) {}
+            mRotationFlags(rotation),
+            mHintForSeamlessTransition(hintForSeamlessTransition) {}
 
     static std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()> fromTraverseLayersLambda(
             std::function<void(const LayerVector::Visitor&)> traverseLayers) {
@@ -90,6 +92,10 @@
     // capture operation.
     virtual sp<Layer> getParentLayer() const { return nullptr; }
 
+    // Returns whether the render result may be used for system animations that
+    // must preserve the exact colors of the display.
+    bool getHintForSeamlessTransition() const { return mHintForSeamlessTransition; }
+
 protected:
     const bool mAllowSecureLayers;
 
@@ -98,7 +104,7 @@
     const ui::Dataspace mReqDataSpace;
     const CaptureFill mCaptureFill;
     const RotationFlags mRotationFlags;
-    const Rect mLayerStackSpaceRect;
+    const bool mHintForSeamlessTransition;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/Android.bp b/services/surfaceflinger/Scheduler/Android.bp
index 5bd8a99..d5d8688 100644
--- a/services/surfaceflinger/Scheduler/Android.bp
+++ b/services/surfaceflinger/Scheduler/Android.bp
@@ -60,7 +60,4 @@
         "libgtest",
         "libscheduler",
     ],
-    sanitize: {
-        address: true,
-    },
 }
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 57661f1..281b0ae 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -42,6 +42,7 @@
 #include <utils/Errors.h>
 #include <utils/Trace.h>
 
+#include <scheduler/VsyncConfig.h>
 #include "DisplayHardware/DisplayMode.h"
 #include "FrameTimeline.h"
 #include "VSyncDispatch.h"
@@ -525,7 +526,7 @@
 
 bool EventThread::shouldConsumeEvent(const DisplayEventReceiver::Event& event,
                                      const sp<EventThreadConnection>& connection) const {
-    const auto throttleVsync = [&] {
+    const auto throttleVsync = [&]() REQUIRES(mMutex) {
         const auto& vsyncData = event.vsync.vsyncData;
         if (connection->frameRate.isValid()) {
             return !mVsyncSchedule->getTracker()
@@ -597,25 +598,57 @@
                                         nsecs_t timestamp,
                                         nsecs_t preferredExpectedPresentationTime,
                                         nsecs_t preferredDeadlineTimestamp) const {
+    uint32_t currentIndex = 0;
     // Add 1 to ensure the preferredFrameTimelineIndex entry (when multiplier == 0) is included.
-    for (int64_t multiplier = -VsyncEventData::kFrameTimelinesLength + 1, currentIndex = 0;
-         currentIndex < VsyncEventData::kFrameTimelinesLength; multiplier++) {
+    for (int64_t multiplier = -VsyncEventData::kFrameTimelinesCapacity + 1;
+         currentIndex < VsyncEventData::kFrameTimelinesCapacity; multiplier++) {
         nsecs_t deadlineTimestamp = preferredDeadlineTimestamp + multiplier * frameInterval;
-        // Valid possible frame timelines must have future values.
-        if (deadlineTimestamp > timestamp) {
-            if (multiplier == 0) {
-                outVsyncEventData.preferredFrameTimelineIndex = currentIndex;
-            }
-            nsecs_t expectedPresentationTime =
-                    preferredExpectedPresentationTime + multiplier * frameInterval;
-            outVsyncEventData.frameTimelines[currentIndex] =
-                    {.vsyncId =
-                             generateToken(timestamp, deadlineTimestamp, expectedPresentationTime),
-                     .deadlineTimestamp = deadlineTimestamp,
-                     .expectedPresentationTime = expectedPresentationTime};
-            currentIndex++;
+        // Valid possible frame timelines must have future values, so find a later frame timeline.
+        if (deadlineTimestamp <= timestamp) {
+            continue;
         }
+
+        nsecs_t expectedPresentationTime =
+                preferredExpectedPresentationTime + multiplier * frameInterval;
+        if (expectedPresentationTime >= preferredExpectedPresentationTime +
+                    scheduler::VsyncConfig::kEarlyLatchMaxThreshold.count()) {
+            if (currentIndex == 0) {
+                ALOGW("%s: Expected present time is too far in the future but no timelines are "
+                      "valid. preferred EPT=%" PRId64 ", Calculated EPT=%" PRId64
+                      ", multiplier=%" PRId64 ", frameInterval=%" PRId64 ", threshold=%" PRId64,
+                      __func__, preferredExpectedPresentationTime, expectedPresentationTime,
+                      multiplier, frameInterval,
+                      static_cast<int64_t>(
+                              scheduler::VsyncConfig::kEarlyLatchMaxThreshold.count()));
+            }
+            break;
+        }
+
+        if (multiplier == 0) {
+            outVsyncEventData.preferredFrameTimelineIndex = currentIndex;
+        }
+
+        outVsyncEventData.frameTimelines[currentIndex] =
+                {.vsyncId = generateToken(timestamp, deadlineTimestamp, expectedPresentationTime),
+                 .deadlineTimestamp = deadlineTimestamp,
+                 .expectedPresentationTime = expectedPresentationTime};
+        currentIndex++;
     }
+
+    if (currentIndex == 0) {
+        ALOGW("%s: No timelines are valid. preferred EPT=%" PRId64 ", frameInterval=%" PRId64
+              ", threshold=%" PRId64,
+              __func__, preferredExpectedPresentationTime, frameInterval,
+              static_cast<int64_t>(scheduler::VsyncConfig::kEarlyLatchMaxThreshold.count()));
+        outVsyncEventData.frameTimelines[currentIndex] =
+                {.vsyncId = generateToken(timestamp, preferredDeadlineTimestamp,
+                                          preferredExpectedPresentationTime),
+                 .deadlineTimestamp = preferredDeadlineTimestamp,
+                 .expectedPresentationTime = preferredExpectedPresentationTime};
+        currentIndex++;
+    }
+
+    outVsyncEventData.frameTimelinesLength = currentIndex;
 }
 
 void EventThread::dispatchEvent(const DisplayEventReceiver::Event& event,
@@ -692,17 +725,27 @@
 }
 
 void EventThread::onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule> schedule) {
+    // Hold onto the old registration until after releasing the mutex to avoid deadlock.
+    scheduler::VSyncCallbackRegistration oldRegistration =
+            onNewVsyncScheduleInternal(std::move(schedule));
+}
+
+scheduler::VSyncCallbackRegistration EventThread::onNewVsyncScheduleInternal(
+        std::shared_ptr<scheduler::VsyncSchedule> schedule) {
     std::lock_guard<std::mutex> lock(mMutex);
     const bool reschedule = mVsyncRegistration.cancel() == scheduler::CancelResult::Cancelled;
     mVsyncSchedule = std::move(schedule);
-    mVsyncRegistration =
-            scheduler::VSyncCallbackRegistration(mVsyncSchedule->getDispatch(),
-                                                 createDispatchCallback(), mThreadName);
+    auto oldRegistration =
+            std::exchange(mVsyncRegistration,
+                          scheduler::VSyncCallbackRegistration(mVsyncSchedule->getDispatch(),
+                                                               createDispatchCallback(),
+                                                               mThreadName));
     if (reschedule) {
         mVsyncRegistration.schedule({.workDuration = mWorkDuration.get().count(),
                                      .readyDuration = mReadyDuration.count(),
                                      .earliestVsync = mLastVsyncCallbackTime.ns()});
     }
+    return oldRegistration;
 }
 
 scheduler::VSyncDispatch::Callback EventThread::createDispatchCallback() {
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index 87e20a0..684745b 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -174,7 +174,7 @@
 
     size_t getEventThreadConnectionCount() override;
 
-    void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) override;
+    void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) override EXCLUDES(mMutex);
 
 private:
     friend EventThreadTest;
@@ -201,11 +201,16 @@
 
     scheduler::VSyncDispatch::Callback createDispatchCallback();
 
+    // Returns the old registration so it can be destructed outside the lock to
+    // avoid deadlock.
+    scheduler::VSyncCallbackRegistration onNewVsyncScheduleInternal(
+            std::shared_ptr<scheduler::VsyncSchedule>) EXCLUDES(mMutex);
+
     const char* const mThreadName;
     TracedOrdinal<int> mVsyncTracer;
     TracedOrdinal<std::chrono::nanoseconds> mWorkDuration GUARDED_BY(mMutex);
     std::chrono::nanoseconds mReadyDuration GUARDED_BY(mMutex);
-    std::shared_ptr<scheduler::VsyncSchedule> mVsyncSchedule;
+    std::shared_ptr<scheduler::VsyncSchedule> mVsyncSchedule GUARDED_BY(mMutex);
     TimePoint mLastVsyncCallbackTime GUARDED_BY(mMutex) = TimePoint::now();
     scheduler::VSyncCallbackRegistration mVsyncRegistration GUARDED_BY(mMutex);
     frametimeline::TokenManager* const mTokenManager;
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 5a90d58..bae3739 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -78,17 +78,16 @@
                                           .count();
 }
 
-bool LayerInfo::isFrequent(nsecs_t now) const {
-    using fps_approx_ops::operator>=;
+LayerInfo::Frequent LayerInfo::isFrequent(nsecs_t now) const {
     // If we know nothing about this layer (e.g. after touch event),
     // we consider it as frequent as it might be the start of an animation.
     if (mFrameTimes.size() < kFrequentLayerWindowSize) {
-        return true;
+        return {/* isFrequent */ true, /* clearHistory */ false, /* isConclusive */ true};
     }
 
     // Non-active layers are also infrequent
     if (mLastUpdatedTime < getActiveLayerThreshold(now)) {
-        return false;
+        return {/* isFrequent */ false, /* clearHistory */ false, /* isConclusive */ true};
     }
 
     // We check whether we can classify this layer as frequent or infrequent:
@@ -111,12 +110,20 @@
     }
 
     if (isFrequent || isInfrequent) {
-        return isFrequent;
+        // If the layer was previously inconclusive, we clear
+        // the history as indeterminate layers changed to frequent,
+        // and we should not look at the stale data.
+        return {isFrequent, isFrequent && !mIsFrequencyConclusive, /* isConclusive */ true};
     }
 
     // If we can't determine whether the layer is frequent or not, we return
-    // the last known classification.
-    return !mLastRefreshRate.infrequent;
+    // the last known classification and mark the layer frequency as inconclusive.
+    isFrequent = !mLastRefreshRate.infrequent;
+
+    // If the layer was previously tagged as animating, we clear
+    // the history as it is likely the layer just changed its behavior,
+    // and we should not look at stale data.
+    return {isFrequent, isFrequent && mLastRefreshRate.animating, /* isConclusive */ false};
 }
 
 Fps LayerInfo::getFps(nsecs_t now) const {
@@ -273,19 +280,18 @@
         return {LayerHistory::LayerVoteType::Max, Fps()};
     }
 
-    if (!isFrequent(now)) {
+    const LayerInfo::Frequent frequent = isFrequent(now);
+    mIsFrequencyConclusive = frequent.isConclusive;
+    if (!frequent.isFrequent) {
         ATRACE_FORMAT_INSTANT("infrequent");
         ALOGV("%s is infrequent", mName.c_str());
         mLastRefreshRate.infrequent = true;
-        // Infrequent layers vote for mininal refresh rate for
+        // Infrequent layers vote for minimal refresh rate for
         // battery saving purposes and also to prevent b/135718869.
         return {LayerHistory::LayerVoteType::Min, Fps()};
     }
 
-    // If the layer was previously tagged as animating or infrequent, we clear
-    // the history as it is likely the layer just changed its behavior
-    // and we should not look at stale data
-    if (mLastRefreshRate.animating || mLastRefreshRate.infrequent) {
+    if (frequent.clearHistory) {
         clearHistory(now);
     }
 
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index a3523ac..c5a6057 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -181,6 +181,7 @@
         mFrameTimeValidSince = std::chrono::time_point<std::chrono::steady_clock>(timePoint);
         mLastRefreshRate = {};
         mRefreshRateHistory.clear();
+        mIsFrequencyConclusive = true;
     }
 
     void clearHistory(nsecs_t now) {
@@ -251,7 +252,15 @@
         static constexpr float MARGIN_CONSISTENT_FPS = 1.0;
     };
 
-    bool isFrequent(nsecs_t now) const;
+    // Represents whether we were able to determine either layer is frequent or infrequent
+    bool mIsFrequencyConclusive = true;
+    struct Frequent {
+        bool isFrequent;
+        bool clearHistory;
+        // Represents whether we were able to determine isFrequent conclusively
+        bool isConclusive;
+    };
+    Frequent isFrequent(nsecs_t now) const;
     bool isAnimating(nsecs_t now) const;
     bool hasEnoughDataForHeuristic() const;
     std::optional<Fps> calculateRefreshRateIfPossible(const RefreshRateSelector&, nsecs_t now);
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp
index 7457b84..18c0a69 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.cpp
+++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp
@@ -78,20 +78,42 @@
 void MessageQueue::initVsync(std::shared_ptr<scheduler::VSyncDispatch> dispatch,
                              frametimeline::TokenManager& tokenManager,
                              std::chrono::nanoseconds workDuration) {
-    std::lock_guard lock(mVsync.mutex);
-    mVsync.workDuration = workDuration;
-    mVsync.tokenManager = &tokenManager;
-    onNewVsyncScheduleLocked(std::move(dispatch));
+    std::unique_ptr<scheduler::VSyncCallbackRegistration> oldRegistration;
+    {
+        std::lock_guard lock(mVsync.mutex);
+        mVsync.workDuration = workDuration;
+        mVsync.tokenManager = &tokenManager;
+        oldRegistration = onNewVsyncScheduleLocked(std::move(dispatch));
+    }
+
+    // See comments in onNewVsyncSchedule. Today, oldRegistration should be
+    // empty, but nothing prevents us from calling initVsync multiple times, so
+    // go ahead and destruct it outside the lock for safety.
+    oldRegistration.reset();
 }
 
 void MessageQueue::onNewVsyncSchedule(std::shared_ptr<scheduler::VSyncDispatch> dispatch) {
-    std::lock_guard lock(mVsync.mutex);
-    onNewVsyncScheduleLocked(std::move(dispatch));
+    std::unique_ptr<scheduler::VSyncCallbackRegistration> oldRegistration;
+    {
+        std::lock_guard lock(mVsync.mutex);
+        oldRegistration = onNewVsyncScheduleLocked(std::move(dispatch));
+    }
+
+    // The old registration needs to be deleted after releasing mVsync.mutex to
+    // avoid deadlock. This is because the callback may be running on the timer
+    // thread. In that case, timerCallback sets
+    // VSyncDispatchTimerQueueEntry::mRunning to true, then attempts to lock
+    // mVsync.mutex. But if it's already locked, the VSyncCallbackRegistration's
+    // destructor has to wait until VSyncDispatchTimerQueueEntry::mRunning is
+    // set back to false, but it won't be until mVsync.mutex is released.
+    oldRegistration.reset();
 }
 
-void MessageQueue::onNewVsyncScheduleLocked(std::shared_ptr<scheduler::VSyncDispatch> dispatch) {
+std::unique_ptr<scheduler::VSyncCallbackRegistration> MessageQueue::onNewVsyncScheduleLocked(
+        std::shared_ptr<scheduler::VSyncDispatch> dispatch) {
     const bool reschedule = mVsync.registration &&
             mVsync.registration->cancel() == scheduler::CancelResult::Cancelled;
+    auto oldRegistration = std::move(mVsync.registration);
     mVsync.registration = std::make_unique<
             scheduler::VSyncCallbackRegistration>(std::move(dispatch),
                                                   std::bind(&MessageQueue::vsyncCallback, this,
@@ -105,6 +127,7 @@
                                                .readyDuration = 0,
                                                .earliestVsync = mVsync.lastCallbackTime.ns()});
     }
+    return oldRegistration;
 }
 
 void MessageQueue::destroyVsync() {
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.h b/services/surfaceflinger/Scheduler/MessageQueue.h
index 9c9b2f3..a523147 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.h
+++ b/services/surfaceflinger/Scheduler/MessageQueue.h
@@ -117,9 +117,9 @@
 
     struct Vsync {
         frametimeline::TokenManager* tokenManager = nullptr;
-        std::unique_ptr<scheduler::VSyncCallbackRegistration> registration;
 
         mutable std::mutex mutex;
+        std::unique_ptr<scheduler::VSyncCallbackRegistration> registration GUARDED_BY(mutex);
         TracedOrdinal<std::chrono::nanoseconds> workDuration
                 GUARDED_BY(mutex) = {"VsyncWorkDuration-sf", std::chrono::nanoseconds(0)};
         TimePoint lastCallbackTime GUARDED_BY(mutex);
@@ -129,7 +129,10 @@
 
     Vsync mVsync;
 
-    void onNewVsyncScheduleLocked(std::shared_ptr<scheduler::VSyncDispatch>) REQUIRES(mVsync.mutex);
+    // Returns the old registration so it can be destructed outside the lock to
+    // avoid deadlock.
+    std::unique_ptr<scheduler::VSyncCallbackRegistration> onNewVsyncScheduleLocked(
+            std::shared_ptr<scheduler::VSyncDispatch>) REQUIRES(mVsync.mutex);
 
 public:
     explicit MessageQueue(ICompositor&);
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index f6fe468..f136e9f 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;
@@ -564,7 +564,7 @@
                 continue;
             }
 
-            const bool inPrimaryRange = policy->primaryRanges.physical.includes(modePtr->getFps());
+            const bool inPrimaryRange = policy->primaryRanges.render.includes(fps);
             if ((primaryRangeIsSingleRate || !inPrimaryRange) &&
                 !(layer.focused &&
                   (layer.vote == LayerVoteType::ExplicitDefault ||
@@ -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/RefreshRateStats.h b/services/surfaceflinger/Scheduler/RefreshRateStats.h
index ed65bc6..67e1b9c 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateStats.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateStats.h
@@ -22,6 +22,7 @@
 #include <string>
 
 #include <android-base/stringprintf.h>
+#include <ftl/algorithm.h>
 #include <ftl/small_map.h>
 #include <utils/Timers.h>
 
@@ -82,12 +83,18 @@
         flushTime();
 
         TotalTimes totalTimes = ftl::init::map("ScreenOff", mScreenOffTime);
-        const auto zero = std::chrono::milliseconds::zero();
 
         // Sum the times for modes that map to the same name, e.g. "60 Hz".
         for (const auto& [fps, time] : mFpsTotalTimes) {
             const auto string = to_string(fps);
-            const auto total = std::as_const(totalTimes).get(string).value_or(std::cref(zero));
+            const auto total = std::as_const(totalTimes)
+                                       .get(string)
+                                       .or_else(ftl::static_ref<std::chrono::milliseconds>([] {
+                                           using namespace std::chrono_literals;
+                                           return 0ms;
+                                       }))
+                                       .value();
+
             totalTimes.emplace_or_replace(string, total.get() + time);
         }
 
@@ -114,15 +121,18 @@
         mPreviousRecordedTime = currentTime;
 
         const auto duration = std::chrono::milliseconds{ns2ms(timeElapsed)};
-        const auto zero = std::chrono::milliseconds::zero();
-
         uint32_t fps = 0;
 
         if (mCurrentPowerMode == PowerMode::ON) {
             // Normal power mode is counted under different config modes.
             const auto total = std::as_const(mFpsTotalTimes)
                                        .get(mCurrentRefreshRate)
-                                       .value_or(std::cref(zero));
+                                       .or_else(ftl::static_ref<std::chrono::milliseconds>([] {
+                                           using namespace std::chrono_literals;
+                                           return 0ms;
+                                       }))
+                                       .value();
+
             mFpsTotalTimes.emplace_or_replace(mCurrentRefreshRate, total.get() + duration);
 
             fps = static_cast<uint32_t>(mCurrentRefreshRate.getIntValue());
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index f18dfdc..9319543 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -50,8 +50,9 @@
 #include "FrontEnd/LayerHandle.h"
 #include "OneShotTimer.h"
 #include "SurfaceFlingerProperties.h"
-#include "VSyncPredictor.h"
-#include "VSyncReactor.h"
+#include "VSyncTracker.h"
+#include "VsyncController.h"
+#include "VsyncSchedule.h"
 
 #define RETURN_IF_INVALID_HANDLE(handle, ...)                        \
     do {                                                             \
@@ -109,7 +110,6 @@
 void Scheduler::setPacesetterDisplay(std::optional<PhysicalDisplayId> pacesetterIdOpt) {
     demotePacesetterDisplay();
 
-    std::scoped_lock lock(mDisplayLock);
     promotePacesetterDisplay(pacesetterIdOpt);
 }
 
@@ -120,29 +120,35 @@
 
 void Scheduler::registerDisplayInternal(PhysicalDisplayId displayId,
                                         RefreshRateSelectorPtr selectorPtr,
-                                        std::shared_ptr<VsyncSchedule> vsyncSchedule) {
+                                        VsyncSchedulePtr schedulePtr) {
     demotePacesetterDisplay();
 
-    std::scoped_lock lock(mDisplayLock);
-    mRefreshRateSelectors.emplace_or_replace(displayId, std::move(selectorPtr));
-    mVsyncSchedules.emplace_or_replace(displayId, std::move(vsyncSchedule));
+    std::shared_ptr<VsyncSchedule> pacesetterVsyncSchedule;
+    {
+        std::scoped_lock lock(mDisplayLock);
+        mDisplays.emplace_or_replace(displayId, std::move(selectorPtr), std::move(schedulePtr));
 
-    promotePacesetterDisplay();
+        pacesetterVsyncSchedule = promotePacesetterDisplayLocked();
+    }
+    applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule));
 }
 
 void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) {
     demotePacesetterDisplay();
 
-    std::scoped_lock lock(mDisplayLock);
-    mRefreshRateSelectors.erase(displayId);
-    mVsyncSchedules.erase(displayId);
+    std::shared_ptr<VsyncSchedule> pacesetterVsyncSchedule;
+    {
+        std::scoped_lock lock(mDisplayLock);
+        mDisplays.erase(displayId);
 
-    // Do not allow removing the final display. Code in the scheduler expects
-    // there to be at least one display. (This may be relaxed in the future with
-    // headless virtual display.)
-    LOG_ALWAYS_FATAL_IF(mRefreshRateSelectors.empty(), "Cannot unregister all displays!");
+        // Do not allow removing the final display. Code in the scheduler expects
+        // there to be at least one display. (This may be relaxed in the future with
+        // headless virtual display.)
+        LOG_ALWAYS_FATAL_IF(mDisplays.empty(), "Cannot unregister all displays!");
 
-    promotePacesetterDisplay();
+        pacesetterVsyncSchedule = promotePacesetterDisplayLocked();
+    }
+    applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule));
 }
 
 void Scheduler::run() {
@@ -192,20 +198,27 @@
 
 impl::EventThread::GetVsyncPeriodFunction Scheduler::makeGetVsyncPeriodFunction() const {
     return [this](uid_t uid) {
-        const Fps refreshRate = pacesetterSelectorPtr()->getActiveMode().fps;
-        const nsecs_t currentPeriod =
-                getVsyncSchedule()->period().ns() ?: refreshRate.getPeriodNsecs();
+        const auto [refreshRate, period] = [this] {
+            std::scoped_lock lock(mDisplayLock);
+            const auto pacesetterOpt = pacesetterDisplayLocked();
+            LOG_ALWAYS_FATAL_IF(!pacesetterOpt);
+            const Display& pacesetter = *pacesetterOpt;
+            return std::make_pair(pacesetter.selectorPtr->getActiveMode().fps,
+                                  pacesetter.schedulePtr->period());
+        }();
+
+        const Period currentPeriod = period != Period::zero() ? period : refreshRate.getPeriod();
 
         const auto frameRate = getFrameRateOverride(uid);
         if (!frameRate.has_value()) {
-            return currentPeriod;
+            return currentPeriod.ns();
         }
 
         const auto divisor = RefreshRateSelector::getFrameRateDivisor(refreshRate, *frameRate);
         if (divisor <= 1) {
-            return currentPeriod;
+            return currentPeriod.ns();
         }
-        return currentPeriod * divisor;
+        return currentPeriod.ns() * divisor;
     };
 }
 
@@ -393,35 +406,42 @@
 
 void Scheduler::enableHardwareVsync(PhysicalDisplayId id) {
     auto schedule = getVsyncSchedule(id);
+    LOG_ALWAYS_FATAL_IF(!schedule);
     schedule->enableHardwareVsync(mSchedulerCallback);
 }
 
 void Scheduler::disableHardwareVsync(PhysicalDisplayId id, bool disallow) {
     auto schedule = getVsyncSchedule(id);
+    LOG_ALWAYS_FATAL_IF(!schedule);
     schedule->disableHardwareVsync(mSchedulerCallback, disallow);
 }
 
 void Scheduler::resyncAllToHardwareVsync(bool allowToEnable) {
+    ATRACE_CALL();
     std::scoped_lock lock(mDisplayLock);
     ftl::FakeGuard guard(kMainThreadContext);
 
-    for (const auto& [id, _] : mRefreshRateSelectors) {
+    for (const auto& [id, _] : mDisplays) {
         resyncToHardwareVsyncLocked(id, allowToEnable);
     }
 }
 
 void Scheduler::resyncToHardwareVsyncLocked(PhysicalDisplayId id, bool allowToEnable,
                                             std::optional<Fps> refreshRate) {
-    auto schedule = getVsyncScheduleLocked(id);
-    if (schedule->isHardwareVsyncAllowed(allowToEnable)) {
+    const auto displayOpt = mDisplays.get(id);
+    if (!displayOpt) {
+        ALOGW("%s: Invalid display %s!", __func__, to_string(id).c_str());
+        return;
+    }
+    const Display& display = *displayOpt;
+
+    if (display.schedulePtr->isHardwareVsyncAllowed(allowToEnable)) {
         if (!refreshRate) {
-            auto selectorPtr = mRefreshRateSelectors.get(id);
-            LOG_ALWAYS_FATAL_IF(!selectorPtr);
-            refreshRate = selectorPtr->get()->getActiveMode().modePtr->getFps();
+            refreshRate = display.selectorPtr->getActiveMode().modePtr->getFps();
         }
         if (refreshRate->isValid()) {
-            schedule->startPeriodTransition(mSchedulerCallback, refreshRate->getPeriod(),
-                                            false /* force */);
+            display.schedulePtr->startPeriodTransition(mSchedulerCallback, refreshRate->getPeriod(),
+                                                       false /* force */);
         }
     }
 }
@@ -430,9 +450,13 @@
     std::scoped_lock lock(mDisplayLock);
     ftl::FakeGuard guard(kMainThreadContext);
 
-    auto selectorPtr = mRefreshRateSelectors.get(id);
-    LOG_ALWAYS_FATAL_IF(!selectorPtr);
-    const auto mode = selectorPtr->get()->getActiveMode();
+    const auto displayOpt = mDisplays.get(id);
+    if (!displayOpt) {
+        ALOGW("%s: Invalid display %s!", __func__, to_string(id).c_str());
+        return;
+    }
+    const Display& display = *displayOpt;
+    const auto mode = display.selectorPtr->getActiveMode();
 
     using fps_approx_ops::operator!=;
     LOG_ALWAYS_FATAL_IF(renderFrameRate != mode.fps,
@@ -443,7 +467,7 @@
     ALOGV("%s %s (%s)", __func__, to_string(mode.fps).c_str(),
           to_string(mode.modePtr->getFps()).c_str());
 
-    getVsyncScheduleLocked(id)->getTracker().setRenderRate(renderFrameRate);
+    display.schedulePtr->getTracker().setRenderRate(renderFrameRate);
 }
 
 void Scheduler::resync() {
@@ -462,12 +486,18 @@
     const auto hwcVsyncPeriod = ftl::Optional(hwcVsyncPeriodIn).transform([](nsecs_t nanos) {
         return Period::fromNs(nanos);
     });
-    return getVsyncSchedule(id)->addResyncSample(mSchedulerCallback, TimePoint::fromNs(timestamp),
-                                                 hwcVsyncPeriod);
+    auto schedule = getVsyncSchedule(id);
+    if (!schedule) {
+        ALOGW("%s: Invalid display %s!", __func__, to_string(id).c_str());
+        return false;
+    }
+    return schedule->addResyncSample(mSchedulerCallback, TimePoint::fromNs(timestamp),
+                                     hwcVsyncPeriod);
 }
 
 void Scheduler::addPresentFence(PhysicalDisplayId id, std::shared_ptr<FenceTime> fence) {
     auto schedule = getVsyncSchedule(id);
+    LOG_ALWAYS_FATAL_IF(!schedule);
     const bool needMoreSignals = schedule->getController().addPresentFence(std::move(fence));
     if (needMoreSignals) {
         schedule->enableHardwareVsync(mSchedulerCallback);
@@ -537,6 +567,7 @@
     {
         std::scoped_lock lock(mDisplayLock);
         auto vsyncSchedule = getVsyncScheduleLocked(id);
+        LOG_ALWAYS_FATAL_IF(!vsyncSchedule);
         vsyncSchedule->getController().setDisplayPowerMode(powerMode);
     }
     if (!isPacesetter) return;
@@ -550,22 +581,26 @@
     mLayerHistory.clear();
 }
 
-std::shared_ptr<const VsyncSchedule> Scheduler::getVsyncSchedule(
-        std::optional<PhysicalDisplayId> idOpt) const {
+auto Scheduler::getVsyncSchedule(std::optional<PhysicalDisplayId> idOpt) const
+        -> ConstVsyncSchedulePtr {
     std::scoped_lock lock(mDisplayLock);
     return getVsyncScheduleLocked(idOpt);
 }
 
-std::shared_ptr<const VsyncSchedule> Scheduler::getVsyncScheduleLocked(
-        std::optional<PhysicalDisplayId> idOpt) const {
+auto Scheduler::getVsyncScheduleLocked(std::optional<PhysicalDisplayId> idOpt) const
+        -> ConstVsyncSchedulePtr {
     ftl::FakeGuard guard(kMainThreadContext);
+
     if (!idOpt) {
         LOG_ALWAYS_FATAL_IF(!mPacesetterDisplayId, "Missing a pacesetter!");
         idOpt = mPacesetterDisplayId;
     }
-    auto scheduleOpt = mVsyncSchedules.get(*idOpt);
-    LOG_ALWAYS_FATAL_IF(!scheduleOpt);
-    return std::const_pointer_cast<const VsyncSchedule>(scheduleOpt->get());
+
+    const auto displayOpt = mDisplays.get(*idOpt);
+    if (!displayOpt) {
+        return nullptr;
+    }
+    return displayOpt->get().schedulePtr;
 }
 
 void Scheduler::kernelIdleTimerCallback(TimerState state) {
@@ -589,9 +624,9 @@
         // need to update the VsyncController model anyway.
         std::scoped_lock lock(mDisplayLock);
         ftl::FakeGuard guard(kMainThreadContext);
-        constexpr bool disallow = false;
-        for (auto& [_, schedule] : mVsyncSchedules) {
-            schedule->disableHardwareVsync(mSchedulerCallback, disallow);
+        for (const auto& [_, display] : mDisplays) {
+            constexpr bool kDisallow = false;
+            display.schedulePtr->disableHardwareVsync(mSchedulerCallback, kDisallow);
         }
     }
 
@@ -656,12 +691,12 @@
                             to_string(*mPacesetterDisplayId).c_str());
         getVsyncScheduleLocked()->dump(out);
     }
-    for (auto& [id, vsyncSchedule] : mVsyncSchedules) {
+    for (auto& [id, display] : mDisplays) {
         if (id == mPacesetterDisplayId) {
             continue;
         }
         base::StringAppendF(&out, "VsyncSchedule for follower %s:\n", to_string(id).c_str());
-        vsyncSchedule->dump(out);
+        display.schedulePtr->dump(out);
     }
 }
 
@@ -678,33 +713,57 @@
 }
 
 void Scheduler::promotePacesetterDisplay(std::optional<PhysicalDisplayId> pacesetterIdOpt) {
+    std::shared_ptr<VsyncSchedule> pacesetterVsyncSchedule;
+
+    {
+        std::scoped_lock lock(mDisplayLock);
+        pacesetterVsyncSchedule = promotePacesetterDisplayLocked(pacesetterIdOpt);
+    }
+
+    applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule));
+}
+
+std::shared_ptr<VsyncSchedule> Scheduler::promotePacesetterDisplayLocked(
+        std::optional<PhysicalDisplayId> pacesetterIdOpt) {
     // TODO(b/241286431): Choose the pacesetter display.
-    mPacesetterDisplayId = pacesetterIdOpt.value_or(mRefreshRateSelectors.begin()->first);
+    mPacesetterDisplayId = pacesetterIdOpt.value_or(mDisplays.begin()->first);
     ALOGI("Display %s is the pacesetter", to_string(*mPacesetterDisplayId).c_str());
 
-    auto vsyncSchedule = getVsyncScheduleLocked(*mPacesetterDisplayId);
-    if (const auto pacesetterPtr = pacesetterSelectorPtrLocked()) {
-        pacesetterPtr->setIdleTimerCallbacks(
+    std::shared_ptr<VsyncSchedule> newVsyncSchedulePtr;
+    if (const auto pacesetterOpt = pacesetterDisplayLocked()) {
+        const Display& pacesetter = *pacesetterOpt;
+
+        pacesetter.selectorPtr->setIdleTimerCallbacks(
                 {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); },
                               .onExpired = [this] { idleTimerCallback(TimerState::Expired); }},
                  .kernel = {.onReset = [this] { kernelIdleTimerCallback(TimerState::Reset); },
                             .onExpired =
                                     [this] { kernelIdleTimerCallback(TimerState::Expired); }}});
 
-        pacesetterPtr->startIdleTimer();
+        pacesetter.selectorPtr->startIdleTimer();
 
-        const Fps refreshRate = pacesetterPtr->getActiveMode().modePtr->getFps();
-        vsyncSchedule->startPeriodTransition(mSchedulerCallback, refreshRate.getPeriod(),
-                                             true /* force */);
+        newVsyncSchedulePtr = pacesetter.schedulePtr;
+
+        const Fps refreshRate = pacesetter.selectorPtr->getActiveMode().modePtr->getFps();
+        newVsyncSchedulePtr->startPeriodTransition(mSchedulerCallback, refreshRate.getPeriod(),
+                                                   true /* force */);
     }
+    return newVsyncSchedulePtr;
+}
 
+void Scheduler::applyNewVsyncSchedule(std::shared_ptr<VsyncSchedule> vsyncSchedule) {
     onNewVsyncSchedule(vsyncSchedule->getDispatch());
+    std::vector<android::EventThread*> threads;
     {
         std::lock_guard<std::mutex> lock(mConnectionsLock);
+        threads.reserve(mConnections.size());
         for (auto& [_, connection] : mConnections) {
-            connection.thread->onNewVsyncSchedule(vsyncSchedule);
+            threads.push_back(connection.thread.get());
         }
     }
+    for (auto* thread : threads) {
+        thread->onNewVsyncSchedule(vsyncSchedule);
+    }
 }
 
 void Scheduler::demotePacesetterDisplay() {
@@ -788,81 +847,35 @@
 
     using RankedRefreshRates = RefreshRateSelector::RankedFrameRates;
     display::PhysicalDisplayVector<RankedRefreshRates> perDisplayRanking;
-
-    // Tallies the score of a refresh rate across `displayCount` displays.
-    struct RefreshRateTally {
-        explicit RefreshRateTally(float score) : score(score) {}
-
-        float score;
-        size_t displayCount = 1;
-    };
-
-    // Chosen to exceed a typical number of refresh rates across displays.
-    constexpr size_t kStaticCapacity = 8;
-    ftl::SmallMap<Fps, RefreshRateTally, kStaticCapacity, FpsApproxEqual> refreshRateTallies;
-
     const auto globalSignals = makeGlobalSignals();
+    Fps pacesetterFps;
 
-    for (const auto& [id, selectorPtr] : mRefreshRateSelectors) {
+    for (const auto& [id, display] : mDisplays) {
         auto rankedFrameRates =
-                selectorPtr->getRankedFrameRates(mPolicy.contentRequirements, globalSignals);
-
-        for (const auto& [frameRateMode, score] : rankedFrameRates.ranking) {
-            const auto [it, inserted] = refreshRateTallies.try_emplace(frameRateMode.fps, score);
-
-            if (!inserted) {
-                auto& tally = it->second;
-                tally.score += score;
-                tally.displayCount++;
-            }
+                display.selectorPtr->getRankedFrameRates(mPolicy.contentRequirements,
+                                                         globalSignals);
+        if (id == *mPacesetterDisplayId) {
+            pacesetterFps = rankedFrameRates.ranking.front().frameRateMode.fps;
         }
-
         perDisplayRanking.push_back(std::move(rankedFrameRates));
     }
 
-    auto maxScoreIt = refreshRateTallies.cbegin();
-
-    // Find the first refresh rate common to all displays.
-    while (maxScoreIt != refreshRateTallies.cend() &&
-           maxScoreIt->second.displayCount != mRefreshRateSelectors.size()) {
-        ++maxScoreIt;
-    }
-
-    if (maxScoreIt != refreshRateTallies.cend()) {
-        // Choose the highest refresh rate common to all displays, if any.
-        for (auto it = maxScoreIt + 1; it != refreshRateTallies.cend(); ++it) {
-            const auto [fps, tally] = *it;
-
-            if (tally.displayCount == mRefreshRateSelectors.size() &&
-                tally.score > maxScoreIt->second.score) {
-                maxScoreIt = it;
-            }
-        }
-    }
-
-    const std::optional<Fps> chosenFps = maxScoreIt != refreshRateTallies.cend()
-            ? std::make_optional(maxScoreIt->first)
-            : std::nullopt;
-
     DisplayModeChoiceMap modeChoices;
-
     using fps_approx_ops::operator==;
 
-    for (auto& [ranking, signals] : perDisplayRanking) {
-        if (!chosenFps) {
-            const auto& [frameRateMode, _] = ranking.front();
-            modeChoices.try_emplace(frameRateMode.modePtr->getPhysicalDisplayId(),
-                                    DisplayModeChoice{frameRateMode, signals});
-            continue;
-        }
+    for (auto& [rankings, signals] : perDisplayRanking) {
+        const auto chosenFrameRateMode =
+                ftl::find_if(rankings,
+                             [&](const auto& ranking) {
+                                 return ranking.frameRateMode.fps == pacesetterFps;
+                             })
+                        .transform([](const auto& scoredFrameRate) {
+                            return scoredFrameRate.get().frameRateMode;
+                        })
+                        .value_or(rankings.front().frameRateMode);
 
-        for (auto& [frameRateMode, _] : ranking) {
-            if (frameRateMode.fps == *chosenFps) {
-                modeChoices.try_emplace(frameRateMode.modePtr->getPhysicalDisplayId(),
-                                        DisplayModeChoice{frameRateMode, signals});
-                break;
-            }
-        }
+        modeChoices.try_emplace(chosenFrameRateMode.modePtr->getPhysicalDisplayId(),
+                                DisplayModeChoice{chosenFrameRateMode, signals});
     }
     return modeChoices;
 }
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 62a5fb2..f13c878 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -50,7 +50,6 @@
 #include "RefreshRateSelector.h"
 #include "Utils/Dumper.h"
 #include "VsyncModulator.h"
-#include "VsyncSchedule.h"
 
 namespace android::scheduler {
 
@@ -93,6 +92,8 @@
 
 using GlobalSignals = RefreshRateSelector::GlobalSignals;
 
+class VsyncSchedule;
+
 class Scheduler : android::impl::MessageQueue {
     using Impl = android::impl::MessageQueue;
 
@@ -108,6 +109,9 @@
 
     using RefreshRateSelectorPtr = std::shared_ptr<RefreshRateSelector>;
 
+    using ConstVsyncSchedulePtr = std::shared_ptr<const VsyncSchedule>;
+    using VsyncSchedulePtr = std::shared_ptr<VsyncSchedule>;
+
     void registerDisplay(PhysicalDisplayId, RefreshRateSelectorPtr) REQUIRES(kMainThreadContext)
             EXCLUDES(mDisplayLock);
     void unregisterDisplay(PhysicalDisplayId) REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
@@ -165,7 +169,7 @@
     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 pacesetter display. In those
     // cases, the caller should pass in the relevant display, and the method
@@ -192,8 +196,8 @@
     // Sets the render rate for the scheduler to run at.
     void setRenderRate(PhysicalDisplayId, Fps);
 
-    void enableHardwareVsync(PhysicalDisplayId);
-    void disableHardwareVsync(PhysicalDisplayId, bool disallow);
+    void enableHardwareVsync(PhysicalDisplayId) REQUIRES(kMainThreadContext);
+    void disableHardwareVsync(PhysicalDisplayId, bool disallow) REQUIRES(kMainThreadContext);
 
     // Resyncs the scheduler to hardware vsync.
     // If allowToEnable is true, then hardware vsync will be turned on.
@@ -215,7 +219,8 @@
     // otherwise.
     bool addResyncSample(PhysicalDisplayId, nsecs_t timestamp,
                          std::optional<nsecs_t> hwcVsyncPeriod);
-    void addPresentFence(PhysicalDisplayId, std::shared_ptr<FenceTime>) EXCLUDES(mDisplayLock);
+    void addPresentFence(PhysicalDisplayId, std::shared_ptr<FenceTime>) EXCLUDES(mDisplayLock)
+            REQUIRES(kMainThreadContext);
 
     // Layers are registered on creation, and unregistered when the weak reference expires.
     void registerLayer(Layer*);
@@ -236,12 +241,12 @@
     void setDisplayPowerMode(PhysicalDisplayId, hal::PowerMode powerMode)
             REQUIRES(kMainThreadContext);
 
-    std::shared_ptr<const VsyncSchedule> getVsyncSchedule(
-            std::optional<PhysicalDisplayId> idOpt = std::nullopt) const EXCLUDES(mDisplayLock);
-    std::shared_ptr<VsyncSchedule> getVsyncSchedule(
-            std::optional<PhysicalDisplayId> idOpt = std::nullopt) EXCLUDES(mDisplayLock) {
-        return std::const_pointer_cast<VsyncSchedule>(
-                static_cast<const Scheduler*>(this)->getVsyncSchedule(idOpt));
+    ConstVsyncSchedulePtr getVsyncSchedule(std::optional<PhysicalDisplayId> = std::nullopt) const
+            EXCLUDES(mDisplayLock);
+
+    VsyncSchedulePtr getVsyncSchedule(std::optional<PhysicalDisplayId> idOpt = std::nullopt)
+            EXCLUDES(mDisplayLock) {
+        return std::const_pointer_cast<VsyncSchedule>(std::as_const(*this).getVsyncSchedule(idOpt));
     }
 
     // Returns true if a given vsync timestamp is considered valid vsync
@@ -321,15 +326,25 @@
     // 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) EXCLUDES(mDisplayLock);
+
+    // Changes to the displays (e.g. registering and unregistering) must be made
+    // while mDisplayLock is locked, and the new pacesetter then must be promoted while
+    // mDisplayLock is still locked. However, a new pacesetter means that
+    // MessageQueue and EventThread need to use the new pacesetter's
+    // VsyncSchedule, and this must happen while mDisplayLock is *not* locked,
+    // or else we may deadlock with EventThread.
+    std::shared_ptr<VsyncSchedule> promotePacesetterDisplayLocked(
+            std::optional<PhysicalDisplayId> pacesetterIdOpt = std::nullopt)
             REQUIRES(kMainThreadContext, mDisplayLock);
+    void applyNewVsyncSchedule(std::shared_ptr<VsyncSchedule>) EXCLUDES(mDisplayLock);
 
     // 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)
-            EXCLUDES(mDisplayLock);
+    void registerDisplayInternal(PhysicalDisplayId, RefreshRateSelectorPtr, VsyncSchedulePtr)
+            REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
 
     struct Policy;
 
@@ -408,16 +423,37 @@
     // must lock for writes but not reads. See also mPolicyLock for locking order.
     mutable std::mutex mDisplayLock;
 
-    display::PhysicalDisplayMap<PhysicalDisplayId, RefreshRateSelectorPtr> mRefreshRateSelectors
-            GUARDED_BY(mDisplayLock) GUARDED_BY(kMainThreadContext);
+    struct Display {
+        Display(RefreshRateSelectorPtr selectorPtr, VsyncSchedulePtr schedulePtr)
+              : selectorPtr(std::move(selectorPtr)), schedulePtr(std::move(schedulePtr)) {}
 
-    // TODO (b/266715559): Store in the same map as mRefreshRateSelectors.
-    display::PhysicalDisplayMap<PhysicalDisplayId, std::shared_ptr<VsyncSchedule>> mVsyncSchedules
-            GUARDED_BY(mDisplayLock) GUARDED_BY(kMainThreadContext);
+        // Effectively const except in move constructor.
+        RefreshRateSelectorPtr selectorPtr;
+        VsyncSchedulePtr schedulePtr;
+    };
+
+    using DisplayRef = std::reference_wrapper<Display>;
+    using ConstDisplayRef = std::reference_wrapper<const Display>;
+
+    display::PhysicalDisplayMap<PhysicalDisplayId, Display> mDisplays GUARDED_BY(mDisplayLock)
+            GUARDED_BY(kMainThreadContext);
 
     ftl::Optional<PhysicalDisplayId> mPacesetterDisplayId GUARDED_BY(mDisplayLock)
             GUARDED_BY(kMainThreadContext);
 
+    ftl::Optional<DisplayRef> pacesetterDisplayLocked() REQUIRES(mDisplayLock) {
+        return static_cast<const Scheduler*>(this)->pacesetterDisplayLocked().transform(
+                [](const Display& display) { return std::ref(const_cast<Display&>(display)); });
+    }
+
+    ftl::Optional<ConstDisplayRef> pacesetterDisplayLocked() const REQUIRES(mDisplayLock) {
+        ftl::FakeGuard guard(kMainThreadContext);
+        return mPacesetterDisplayId.and_then([this](PhysicalDisplayId pacesetterId)
+                                                     REQUIRES(mDisplayLock, kMainThreadContext) {
+                                                         return mDisplays.get(pacesetterId);
+                                                     });
+    }
+
     RefreshRateSelectorPtr pacesetterSelectorPtr() const EXCLUDES(mDisplayLock) {
         std::scoped_lock lock(mDisplayLock);
         return pacesetterSelectorPtrLocked();
@@ -425,19 +461,17 @@
 
     RefreshRateSelectorPtr pacesetterSelectorPtrLocked() const REQUIRES(mDisplayLock) {
         ftl::FakeGuard guard(kMainThreadContext);
-        const RefreshRateSelectorPtr noPacesetter;
-        return mPacesetterDisplayId
-                .and_then([this](PhysicalDisplayId pacesetterId)
-                                  REQUIRES(mDisplayLock, kMainThreadContext) {
-                                      return mRefreshRateSelectors.get(pacesetterId);
-                                  })
-                .value_or(std::cref(noPacesetter));
+        return pacesetterDisplayLocked()
+                .transform([](const Display& display) { return display.selectorPtr; })
+                .or_else([] { return std::optional<RefreshRateSelectorPtr>(nullptr); })
+                .value();
     }
 
-    std::shared_ptr<const VsyncSchedule> getVsyncScheduleLocked(
-            std::optional<PhysicalDisplayId> idOpt = std::nullopt) const REQUIRES(mDisplayLock);
-    std::shared_ptr<VsyncSchedule> getVsyncScheduleLocked(
-            std::optional<PhysicalDisplayId> idOpt = std::nullopt) REQUIRES(mDisplayLock) {
+    ConstVsyncSchedulePtr getVsyncScheduleLocked(
+            std::optional<PhysicalDisplayId> = std::nullopt) const REQUIRES(mDisplayLock);
+
+    VsyncSchedulePtr getVsyncScheduleLocked(std::optional<PhysicalDisplayId> idOpt = std::nullopt)
+            REQUIRES(mDisplayLock) {
         return std::const_pointer_cast<VsyncSchedule>(
                 static_cast<const Scheduler*>(this)->getVsyncScheduleLocked(idOpt));
     }
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatch.h b/services/surfaceflinger/Scheduler/VSyncDispatch.h
index 77875e3..c3a952f 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatch.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatch.h
@@ -155,10 +155,6 @@
     VSyncDispatch& operator=(const VSyncDispatch&) = delete;
 };
 
-/*
- * Helper class to operate on registered callbacks. It is up to user of the class to ensure
- * that VsyncDispatch lifetime exceeds the lifetime of VSyncCallbackRegistation.
- */
 class VSyncCallbackRegistration {
 public:
     VSyncCallbackRegistration(std::shared_ptr<VSyncDispatch>, VSyncDispatch::Callback,
@@ -178,9 +174,10 @@
     CancelResult cancel();
 
 private:
+    friend class VSyncCallbackRegistrationTest;
+
     std::shared_ptr<VSyncDispatch> mDispatch;
-    VSyncDispatch::CallbackToken mToken;
-    bool mValidToken;
+    std::optional<VSyncDispatch::CallbackToken> mToken;
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
index 26389eb..1f922f1 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
@@ -21,12 +21,16 @@
 #include <android-base/stringprintf.h>
 #include <ftl/concat.h>
 #include <utils/Trace.h>
+#include <log/log_main.h>
 
 #include <scheduler/TimeKeeper.h>
 
 #include "VSyncDispatchTimerQueue.h"
 #include "VSyncTracker.h"
 
+#undef LOG_TAG
+#define LOG_TAG "VSyncDispatch"
+
 namespace android::scheduler {
 
 using base::StringAppendF;
@@ -225,6 +229,10 @@
 VSyncDispatchTimerQueue::~VSyncDispatchTimerQueue() {
     std::lock_guard lock(mMutex);
     cancelTimer();
+    for (auto& [_, entry] : mCallbacks) {
+        ALOGE("Forgot to unregister a callback on VSyncDispatch!");
+        entry->ensureNotRunning();
+    }
 }
 
 void VSyncDispatchTimerQueue::cancelTimer() {
@@ -438,47 +446,44 @@
                                                      VSyncDispatch::Callback callback,
                                                      std::string callbackName)
       : mDispatch(std::move(dispatch)),
-        mToken(mDispatch->registerCallback(std::move(callback), std::move(callbackName))),
-        mValidToken(true) {}
+        mToken(mDispatch->registerCallback(std::move(callback), std::move(callbackName))) {}
 
 VSyncCallbackRegistration::VSyncCallbackRegistration(VSyncCallbackRegistration&& other)
-      : mDispatch(std::move(other.mDispatch)),
-        mToken(std::move(other.mToken)),
-        mValidToken(std::move(other.mValidToken)) {
-    other.mValidToken = false;
-}
+      : mDispatch(std::move(other.mDispatch)), mToken(std::exchange(other.mToken, std::nullopt)) {}
 
 VSyncCallbackRegistration& VSyncCallbackRegistration::operator=(VSyncCallbackRegistration&& other) {
+    if (this == &other) return *this;
+    if (mToken) {
+        mDispatch->unregisterCallback(*mToken);
+    }
     mDispatch = std::move(other.mDispatch);
-    mToken = std::move(other.mToken);
-    mValidToken = std::move(other.mValidToken);
-    other.mValidToken = false;
+    mToken = std::exchange(other.mToken, std::nullopt);
     return *this;
 }
 
 VSyncCallbackRegistration::~VSyncCallbackRegistration() {
-    if (mValidToken) mDispatch->unregisterCallback(mToken);
+    if (mToken) mDispatch->unregisterCallback(*mToken);
 }
 
 ScheduleResult VSyncCallbackRegistration::schedule(VSyncDispatch::ScheduleTiming scheduleTiming) {
-    if (!mValidToken) {
+    if (!mToken) {
         return std::nullopt;
     }
-    return mDispatch->schedule(mToken, scheduleTiming);
+    return mDispatch->schedule(*mToken, scheduleTiming);
 }
 
 ScheduleResult VSyncCallbackRegistration::update(VSyncDispatch::ScheduleTiming scheduleTiming) {
-    if (!mValidToken) {
+    if (!mToken) {
         return std::nullopt;
     }
-    return mDispatch->update(mToken, scheduleTiming);
+    return mDispatch->update(*mToken, scheduleTiming);
 }
 
 CancelResult VSyncCallbackRegistration::cancel() {
-    if (!mValidToken) {
+    if (!mToken) {
         return CancelResult::Error;
     }
-    return mDispatch->cancel(mToken);
+    return mDispatch->cancel(*mToken);
 }
 
 } // namespace android::scheduler
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/include/scheduler/VsyncConfig.h b/services/surfaceflinger/Scheduler/include/scheduler/VsyncConfig.h
index 3b1985f..47d95a8 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/VsyncConfig.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/VsyncConfig.h
@@ -22,6 +22,8 @@
 
 namespace android::scheduler {
 
+using namespace std::chrono_literals;
+
 // Phase offsets and work durations for SF and app deadlines from VSYNC.
 struct VsyncConfig {
     nsecs_t sfOffset;
@@ -35,6 +37,10 @@
     }
 
     bool operator!=(const VsyncConfig& other) const { return !(*this == other); }
+
+    // The duration for which SF can delay a frame if it is considered early based on the
+    // VsyncConfig::appWorkDuration.
+    static constexpr std::chrono::nanoseconds kEarlyLatchMaxThreshold = 100ms;
 };
 
 struct VsyncConfigSet {
diff --git a/services/surfaceflinger/ScreenCaptureOutput.cpp b/services/surfaceflinger/ScreenCaptureOutput.cpp
index a1d5cd7..09dac23 100644
--- a/services/surfaceflinger/ScreenCaptureOutput.cpp
+++ b/services/surfaceflinger/ScreenCaptureOutput.cpp
@@ -36,6 +36,7 @@
     output->setLayerFilter({args.layerStack});
     output->setRenderSurface(std::make_unique<ScreenCaptureRenderSurface>(std::move(args.buffer)));
     output->setDisplayBrightness(args.sdrWhitePointNits, args.displayBrightnessNits);
+    output->editState().clientTargetBrightness = args.targetBrightness;
 
     output->setDisplayColorProfile(std::make_unique<compositionengine::impl::DisplayColorProfile>(
             compositionengine::DisplayColorProfileCreationArgsBuilder()
@@ -75,7 +76,6 @@
     auto clientCompositionDisplay =
             compositionengine::impl::Output::generateClientCompositionDisplaySettings();
     clientCompositionDisplay.clip = mRenderArea.getSourceCrop();
-    clientCompositionDisplay.targetLuminanceNits = -1;
     return clientCompositionDisplay;
 }
 
@@ -94,6 +94,16 @@
         }
     }
 
+    if (outputDataspace == ui::Dataspace::BT2020_HLG) {
+        for (auto& layer : clientCompositionLayers) {
+            auto transfer = layer.sourceDataspace & ui::Dataspace::TRANSFER_MASK;
+            if (transfer != static_cast<int32_t>(ui::Dataspace::TRANSFER_HLG) &&
+                transfer != static_cast<int32_t>(ui::Dataspace::TRANSFER_ST2084)) {
+                layer.whitePointNits *= (1000.0f / 203.0f);
+            }
+        }
+    }
+
     Rect sourceCrop = mRenderArea.getSourceCrop();
     compositionengine::LayerFE::LayerSettings fillLayer;
     fillLayer.source.buffer.buffer = nullptr;
diff --git a/services/surfaceflinger/ScreenCaptureOutput.h b/services/surfaceflinger/ScreenCaptureOutput.h
index 4e5a0cc..3c307b0 100644
--- a/services/surfaceflinger/ScreenCaptureOutput.h
+++ b/services/surfaceflinger/ScreenCaptureOutput.h
@@ -33,6 +33,8 @@
     std::shared_ptr<renderengine::ExternalTexture> buffer;
     float sdrWhitePointNits;
     float displayBrightnessNits;
+    // Counterintuitively, when targetBrightness > 1.0 then dim the scene.
+    float targetBrightness;
     bool regionSampling;
 };
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 2c1e333..d55ec99 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -105,10 +105,12 @@
 #include <memory>
 #include <mutex>
 #include <optional>
+#include <string>
 #include <type_traits>
 #include <unordered_map>
 #include <vector>
 
+#include <gui/LayerStatePermissions.h>
 #include <ui/DisplayIdentification.h>
 #include "BackgroundExecutor.h"
 #include "Client.h"
@@ -130,6 +132,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 +295,13 @@
             displayHdrCapabilities.getDesiredMinLuminance()};
 }
 
+uint32_t getLayerIdFromSurfaceControl(sp<SurfaceControl> surfaceControl) {
+    if (!surfaceControl) {
+        return UNASSIGNED_LAYER_ID;
+    }
+    return LayerHandle::getLayerId(surfaceControl->getHandle());
+}
+
 }  // namespace anonymous
 
 // ---------------------------------------------------------------------------
@@ -346,6 +356,8 @@
             PermissionCache::checkPermission(permission, pid, uid);
 }
 
+ui::Transform::RotationFlags SurfaceFlinger::sActiveDisplayRotationFlags = ui::Transform::ROT_0;
+
 SurfaceFlinger::SurfaceFlinger(Factory& factory, SkipInitializationTag)
       : mFactory(factory),
         mPid(getpid()),
@@ -469,10 +481,6 @@
 
     mIgnoreHdrCameraLayers = ignore_hdr_camera_layers(false);
 
-    // Power hint session mode, representing which hint(s) to send: early, late, or both)
-    mPowerHintSessionMode =
-            {.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("persist.debug.sf.enable_layer_lifecycle_manager"s, false);
     mLegacyFrontEndEnabled = !mLayerLifecycleManagerEnabled ||
@@ -497,14 +505,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();
 }
 
@@ -707,12 +714,12 @@
 
         readPersistentProperties();
         mPowerAdvisor->onBootFinished();
-        const bool powerHintEnabled = mFlagManager.use_adpf_cpu_hint();
-        mPowerAdvisor->enablePowerHint(powerHintEnabled);
-        const bool powerHintUsed = mPowerAdvisor->usePowerHintSession();
+        const bool hintSessionEnabled = mFlagManager.use_adpf_cpu_hint();
+        mPowerAdvisor->enablePowerHintSession(hintSessionEnabled);
+        const bool hintSessionUsed = mPowerAdvisor->usePowerHintSession();
         ALOGD("Power hint is %s",
-              powerHintUsed ? "supported" : (powerHintEnabled ? "unsupported" : "disabled"));
-        if (powerHintUsed) {
+              hintSessionUsed ? "supported" : (hintSessionEnabled ? "unsupported" : "disabled"));
+        if (hintSessionUsed) {
             std::optional<pid_t> renderEngineTid = getRenderEngine().getRenderEngineTid();
             std::vector<int32_t> tidList;
             tidList.emplace_back(gettid());
@@ -873,7 +880,9 @@
     mDrawingState = mCurrentState;
 
     onActiveDisplayChangedLocked(nullptr, *display);
-    initializeDisplays();
+
+    static_cast<void>(mScheduler->schedule(
+            [this]() FTL_FAKE_GUARD(kMainThreadContext) { initializeDisplays(); }));
 
     mPowerAdvisor->init();
 
@@ -1140,14 +1149,26 @@
     std::optional<PhysicalDisplayId> displayIdOpt;
     {
         Mutex::Autolock lock(mStateLock);
-        displayIdOpt = getPhysicalDisplayIdLocked(displayToken);
+        if (displayToken) {
+            displayIdOpt = getPhysicalDisplayIdLocked(displayToken);
+            if (!displayIdOpt) {
+                ALOGW("%s: Invalid physical display token %p", __func__, displayToken.get());
+                return NAME_NOT_FOUND;
+            }
+        } else {
+            // TODO (b/277364366): Clients should be updated to pass in the display they
+            // want, rather than us picking an arbitrary one (the active display, in this
+            // case).
+            displayIdOpt = mActiveDisplayId;
+        }
     }
 
-    if (!displayIdOpt) {
-        ALOGE("%s: Invalid physical display token %p", __func__, displayToken.get());
+    const auto schedule = mScheduler->getVsyncSchedule(displayIdOpt);
+    if (!schedule) {
+        ALOGE("%s: Missing VSYNC schedule for display %s!", __func__,
+              to_string(*displayIdOpt).c_str());
         return NAME_NOT_FOUND;
     }
-    const auto schedule = mScheduler->getVsyncSchedule(displayIdOpt);
     outStats->vsyncTime = schedule->vsyncDeadlineAfter(TimePoint::now()).ns();
     outStats->vsyncPeriod = schedule->period().ns();
     return NO_ERROR;
@@ -1589,8 +1610,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);
     }
@@ -2127,7 +2148,9 @@
     static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
         {
             ftl::FakeGuard guard(kMainThreadContext);
-            mScheduler->getVsyncSchedule(id)->setPendingHardwareVsyncState(enabled);
+            if (auto schedule = mScheduler->getVsyncSchedule(id)) {
+                schedule->setPendingHardwareVsyncState(enabled);
+            }
         }
 
         ATRACE_FORMAT("%s (%d) for %" PRIu64 " (main thread)", whence, enabled, id.value);
@@ -2137,7 +2160,14 @@
     }));
 }
 
-auto SurfaceFlinger::getPreviousPresentFence(TimePoint frameTime, Period vsyncPeriod)
+bool SurfaceFlinger::wouldPresentEarly(TimePoint frameTime, Period vsyncPeriod) const {
+    const bool isThreeVsyncsAhead = mExpectedPresentTime - frameTime > 2 * vsyncPeriod;
+    return isThreeVsyncsAhead ||
+            getPreviousPresentFence(frameTime, vsyncPeriod)->getSignalTime() !=
+            Fence::SIGNAL_TIME_PENDING;
+}
+
+auto SurfaceFlinger::getPreviousPresentFence(TimePoint frameTime, Period vsyncPeriod) const
         -> const FenceTimePtr& {
     const bool isTwoVsyncsAhead = mExpectedPresentTime - frameTime > vsyncPeriod;
     const size_t i = static_cast<size_t>(isTwoVsyncsAhead);
@@ -2175,7 +2205,7 @@
     }
 }
 
-bool SurfaceFlinger::updateLayerSnapshotsLegacy(VsyncId vsyncId, LifecycleUpdate& update,
+bool SurfaceFlinger::updateLayerSnapshotsLegacy(VsyncId vsyncId, frontend::Update& update,
                                                 bool transactionsFlushed,
                                                 bool& outTransactionsAreEmpty) {
     bool needsTraversal = false;
@@ -2227,7 +2257,7 @@
     }
 }
 
-bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, LifecycleUpdate& update,
+bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, frontend::Update& update,
                                           bool transactionsFlushed, bool& outTransactionsAreEmpty) {
     using Changes = frontend::RequestedLayerState::Changes;
     ATRACE_NAME("updateLayerSnapshots");
@@ -2291,16 +2321,20 @@
                 sp<Layer> bgColorLayer = getFactory().createEffectLayer(
                         LayerCreationArgs(this, nullptr, layer->name,
                                           ISurfaceComposerClient::eFXSurfaceEffect, LayerMetadata(),
-                                          std::make_optional(layer->parentId), true));
+                                          std::make_optional(layer->id), true));
                 mLegacyLayers[bgColorLayer->sequence] = bgColorLayer;
             }
-            if (!layer->hasReadyFrame()) continue;
+            const bool willReleaseBufferOnLatch = layer->willReleaseBufferOnLatch();
+            if (!layer->hasReadyFrame() && !willReleaseBufferOnLatch) continue;
 
             auto it = mLegacyLayers.find(layer->id);
             LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s",
                                 layer->getDebugString().c_str());
             const bool bgColorOnly =
                     !layer->externalTexture && (layer->bgColorLayerId != UNASSIGNED_LAYER_ID);
+            if (willReleaseBufferOnLatch) {
+                mLayersWithBuffersRemoved.emplace(it->second);
+            }
             it->second->latchBufferImpl(unused, latchTime, bgColorOnly);
             mLayersWithQueuedFrames.emplace(it->second);
         }
@@ -2451,16 +2485,7 @@
 
         mPowerAdvisor->setFrameDelay(frameDelay);
         mPowerAdvisor->setTotalFrameTargetWorkDuration(idealSfWorkDuration);
-
-        const auto& display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get();
-        const Period vsyncPeriod = display->getActiveMode().fps.getPeriod();
-        mPowerAdvisor->setTargetWorkDuration(vsyncPeriod);
-
-        // Send early hint here to make sure there's not another frame pending
-        if (mPowerHintSessionMode.early) {
-            // Send a rough prediction for this frame based on last frame's timing info
-            mPowerAdvisor->sendPredictedWorkDuration();
-        }
+        mPowerAdvisor->updateTargetWorkDuration(vsyncPeriod);
     }
 
     if (mRefreshRateOverlaySpinner) {
@@ -2477,9 +2502,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) {
@@ -2517,11 +2547,11 @@
     }
 
     updateCursorAsync();
-    updateInputFlinger();
+    updateInputFlinger(vsyncId);
 
     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;
 
@@ -2548,6 +2578,7 @@
         if (!dropFrame) {
             refreshArgs.outputs.push_back(display->getCompositionDisplay());
         }
+        display->tracePowerMode();
         displayIds.push_back(display->getId());
     }
     mPowerAdvisor->setDisplays(displayIds);
@@ -2590,7 +2621,7 @@
 
     refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty;
     refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) || mVisibleRegionsDirty;
-    refreshArgs.internalDisplayRotationFlags = DisplayDevice::getPrimaryDisplayRotationFlags();
+    refreshArgs.internalDisplayRotationFlags = getActiveDisplayRotationFlags();
 
     if (CC_UNLIKELY(mDrawingState.colorMatrixChanged)) {
         refreshArgs.colorTransformMatrix = mDrawingState.colorMatrix;
@@ -2604,11 +2635,17 @@
         refreshArgs.devOptFlashDirtyRegionsDelay = std::chrono::milliseconds(mDebugFlashDelay);
     }
 
-    const auto prevVsyncTime = mExpectedPresentTime - mScheduler->getVsyncSchedule()->period();
-    const auto hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration;
+    const Period vsyncPeriod = mScheduler->getVsyncSchedule()->period();
 
-    refreshArgs.earliestPresentTime = prevVsyncTime - hwcMinWorkDuration;
-    refreshArgs.previousPresentFence = mPreviousPresentFences[0].fenceTime;
+    if (!getHwComposer().getComposer()->isSupported(
+                Hwc2::Composer::OptionalFeature::ExpectedPresentTime) &&
+        wouldPresentEarly(frameTime, vsyncPeriod)) {
+        const auto prevVsyncTime = mExpectedPresentTime - vsyncPeriod;
+        const auto hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration;
+
+        refreshArgs.earliestPresentTime = prevVsyncTime - hwcMinWorkDuration;
+    }
+
     refreshArgs.scheduledFrameTime = mScheduler->getScheduledFrameTime();
     refreshArgs.expectedPresentTime = mExpectedPresentTime.ns();
     refreshArgs.hasTrustedPresentationListener = mNumTrustedPresentationListeners > 0;
@@ -2625,8 +2662,10 @@
     for (auto [layer, layerFE] : layers) {
         CompositionResult compositionResult{layerFE->stealCompositionResult()};
         layer->onPreComposition(compositionResult.refreshStartTime);
-        for (auto releaseFence : compositionResult.releaseFences) {
-            layer->onLayerDisplayed(releaseFence);
+        for (auto& [releaseFence, layerStack] : compositionResult.releaseFences) {
+            Layer* clonedFrom = layer->getClonedFrom().get();
+            auto owningLayer = clonedFrom ? clonedFrom : layer;
+            owningLayer->onLayerDisplayed(std::move(releaseFence), layerStack);
         }
         if (compositionResult.lastClientCompositionFence) {
             layer->setWasClientComposed(compositionResult.lastClientCompositionFence);
@@ -2637,12 +2676,11 @@
 
     // Send a power hint hint after presentation is finished
     if (mPowerHintSessionEnabled) {
-        mPowerAdvisor->setSfPresentTiming(TimePoint::fromNs(mPreviousPresentFences[0]
-                                                                    .fenceTime->getSignalTime()),
-                                          TimePoint::now());
-        if (mPowerHintSessionMode.late) {
-            mPowerAdvisor->sendActualWorkDuration();
-        }
+        const nsecs_t pastPresentTime =
+                getPreviousPresentFence(frameTime, vsyncPeriod)->getSignalTime();
+
+        mPowerAdvisor->setSfPresentTiming(TimePoint::fromNs(pastPresentTime), TimePoint::now());
+        mPowerAdvisor->reportActualWorkDuration();
     }
 
     if (mScheduler->onPostComposition(presentTime)) {
@@ -2692,7 +2730,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;
@@ -2745,7 +2783,7 @@
     // RANGE_EXTENDED layers may identify themselves as being "HDR" via a desired sdr/hdr ratio
     if ((snapshot.dataspace & (int32_t)Dataspace::RANGE_MASK) ==
                 (int32_t)Dataspace::RANGE_EXTENDED &&
-        snapshot.desiredSdrHdrRatio > 1.01f) {
+        snapshot.desiredHdrSdrRatio > 1.01f) {
         return true;
     }
     return false;
@@ -2839,6 +2877,32 @@
     const CompositorTiming compositorTiming(vsyncDeadline.ns(), vsyncPeriod.ns(), vsyncPhase,
                                             presentLatency.ns());
 
+    display::DisplayMap<ui::LayerStack, const DisplayDevice*> layerStackToDisplay;
+    {
+        if (!mLayersWithBuffersRemoved.empty() || mNumTrustedPresentationListeners > 0) {
+            Mutex::Autolock lock(mStateLock);
+            for (const auto& [token, display] : mDisplays) {
+                layerStackToDisplay.emplace_or_replace(display->getLayerStack(), display.get());
+            }
+        }
+    }
+
+    for (auto layer : mLayersWithBuffersRemoved) {
+        std::vector<ui::LayerStack> previouslyPresentedLayerStacks =
+                std::move(layer->mPreviouslyPresentedLayerStacks);
+        layer->mPreviouslyPresentedLayerStacks.clear();
+        for (auto layerStack : previouslyPresentedLayerStacks) {
+            auto optDisplay = layerStackToDisplay.get(layerStack);
+            if (optDisplay && !optDisplay->get()->isVirtual()) {
+                auto fence = getHwComposer().getPresentFence(optDisplay->get()->getPhysicalId());
+                layer->onLayerDisplayed(ftl::yield<FenceResult>(fence).share(),
+                                        ui::INVALID_LAYER_STACK);
+            }
+        }
+        layer->releasePendingBuffer(presentTime.ns());
+    }
+    mLayersWithBuffersRemoved.clear();
+
     for (const auto& layer: mLayersWithQueuedFrames) {
         layer->onPostComposition(defaultDisplay, glCompositionDoneFenceTime, presentFenceTime,
                                  compositorTiming);
@@ -2882,7 +2946,10 @@
                         const auto* outputLayer =
                             compositionDisplay->getOutputLayerForLayer(layerFe);
                         if (outputLayer) {
-                            info.mergeDesiredRatio(snapshot.desiredSdrHdrRatio);
+                            const float desiredHdrSdrRatio = snapshot.desiredHdrSdrRatio <= 1.f
+                                    ? std::numeric_limits<float>::infinity()
+                                    : snapshot.desiredHdrSdrRatio;
+                            info.mergeDesiredRatio(desiredHdrSdrRatio);
                             info.numberOfHdrLayers++;
                             const auto displayFrame = outputLayer->getState().displayFrame;
                             const int32_t area = displayFrame.width() * displayFrame.height();
@@ -2962,14 +3029,6 @@
     }
 
     if (mNumTrustedPresentationListeners > 0) {
-        display::DisplayMap<ui::LayerStack, const DisplayDevice*> layerStackToDisplay;
-        {
-            Mutex::Autolock lock(mStateLock);
-            for (const auto& [token, display] : mDisplays) {
-                layerStackToDisplay.emplace_or_replace(display->getLayerStack(), display.get());
-            }
-        }
-
         // We avoid any reverse traversal upwards so this shouldn't be too expensive
         traverseLegacyLayers([&](Layer* layer) {
             if (!layer->hasTrustedPresentationListener()) {
@@ -3485,7 +3544,7 @@
 
             // TODO(b/175678251) Call a listener instead.
             if (currentState.physical->hwcDisplayId == getHwComposer().getPrimaryHwcDisplayId()) {
-                updateActiveDisplayVsyncLocked(*display);
+                resetPhaseConfiguration(display->getActiveMode().fps);
             }
         }
         return;
@@ -3506,6 +3565,8 @@
                                    currentState.orientedDisplaySpaceRect);
             if (display->getId() == mActiveDisplayId) {
                 mActiveDisplayTransformHint = display->getTransformHint();
+                sActiveDisplayRotationFlags =
+                        ui::Transform::toRotationFlags(display->getOrientation());
             }
         }
         if (currentState.width != drawingState.width ||
@@ -3519,9 +3580,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);
 }
@@ -3674,11 +3737,11 @@
     doCommitTransactions();
 }
 
-void SurfaceFlinger::updateInputFlinger() {
-    ATRACE_CALL();
-    if (!mInputFlinger) {
+void SurfaceFlinger::updateInputFlinger(VsyncId vsyncId) {
+    if (!mInputFlinger || (!mUpdateInputInfo && mInputWindowCommands.empty())) {
         return;
     }
+    ATRACE_CALL();
 
     std::vector<WindowInfo> windowInfos;
     std::vector<DisplayInfo> displayInfos;
@@ -3686,9 +3749,21 @@
     if (mUpdateInputInfo) {
         mUpdateInputInfo = false;
         updateWindowInfo = true;
+        mLastInputFlingerUpdateVsyncId = vsyncId;
+        mLastInputFlingerUpdateTimestamp = systemTime();
         buildWindowInfos(windowInfos, displayInfos);
-    } else if (mInputWindowCommands.empty()) {
-        return;
+    }
+
+    std::unordered_set<int32_t> visibleWindowIds;
+    for (WindowInfo& windowInfo : windowInfos) {
+        if (!windowInfo.inputConfig.test(WindowInfo::InputConfig::NOT_VISIBLE)) {
+            visibleWindowIds.insert(windowInfo.id);
+        }
+    }
+    bool visibleWindowsChanged = false;
+    if (visibleWindowIds != mVisibleWindowIds) {
+        visibleWindowsChanged = true;
+        mVisibleWindowIds = std::move(visibleWindowIds);
     }
 
     BackgroundExecutor::getInstance().sendCallbacks({[updateWindowInfo,
@@ -3696,12 +3771,18 @@
                                                       displayInfos = std::move(displayInfos),
                                                       inputWindowCommands =
                                                               std::move(mInputWindowCommands),
-                                                      inputFlinger = mInputFlinger, this]() {
+                                                      inputFlinger = mInputFlinger, this,
+                                                      visibleWindowsChanged]() {
         ATRACE_NAME("BackgroundExecutor::updateInputFlinger");
         if (updateWindowInfo) {
             mWindowInfosListenerInvoker
-                    ->windowInfosChanged(windowInfos, displayInfos,
-                                         inputWindowCommands.windowInfosReportedListeners);
+                    ->windowInfosChanged(std::move(windowInfos), std::move(displayInfos),
+                                         std::move(
+                                                 inputWindowCommands.windowInfosReportedListeners),
+                                         /* forceImmediateCall= */ visibleWindowsChanged ||
+                                                 !inputWindowCommands.focusRequests.empty(),
+                                         mLastInputFlingerUpdateVsyncId,
+                                         mLastInputFlingerUpdateTimestamp);
         } else {
             // If there are listeners but no changes to input windows, call the listeners
             // immediately.
@@ -3948,8 +4029,24 @@
     }
 
     commitOffscreenLayers();
-    if (mNumClones > 0) {
-        mDrawingState.traverse([&](Layer* layer) { layer->updateMirrorInfo(); });
+    if (mLayerMirrorRoots.size() > 0) {
+        std::deque<Layer*> pendingUpdates;
+        pendingUpdates.insert(pendingUpdates.end(), mLayerMirrorRoots.begin(),
+                              mLayerMirrorRoots.end());
+        std::vector<Layer*> needsUpdating;
+        for (Layer* cloneRoot : mLayerMirrorRoots) {
+            pendingUpdates.pop_front();
+            if (cloneRoot->isRemovedFromCurrentState()) {
+                continue;
+            }
+            if (cloneRoot->updateMirrorInfo(pendingUpdates)) {
+            } else {
+                needsUpdating.push_back(cloneRoot);
+            }
+        }
+        for (Layer* cloneRoot : needsUpdating) {
+            cloneRoot->updateMirrorInfo({});
+        }
     }
 }
 
@@ -3999,7 +4096,7 @@
             }
         }
 
-        if (layer->hasReadyFrame()) {
+        if (layer->hasReadyFrame() || layer->willReleaseBufferOnLatch()) {
             frameQueued = true;
             mLayersWithQueuedFrames.emplace(sp<Layer>::fromExisting(layer));
         } else {
@@ -4030,6 +4127,9 @@
         Mutex::Autolock lock(mStateLock);
 
         for (const auto& layer : mLayersWithQueuedFrames) {
+            if (layer->willReleaseBufferOnLatch()) {
+                mLayersWithBuffersRemoved.emplace(layer);
+            }
             if (layer->latchBuffer(visibleRegions, latchTime)) {
                 mLayersPendingRefresh.push_back(layer);
                 newDataLatched = true;
@@ -4053,7 +4153,7 @@
         mBootStage = BootStage::BOOTANIMATION;
     }
 
-    if (mNumClones > 0) {
+    if (mLayerMirrorRoots.size() > 0) {
         mDrawingState.traverse([&](Layer* layer) { layer->updateCloneBufferInfo(); });
     }
 
@@ -4061,26 +4161,61 @@
     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) {
         ALOGE("AddClientLayer failed, mNumLayers (%zu) >= MAX_LAYERS (%zu)", mNumLayers.load(),
               MAX_LAYERS);
         static_cast<void>(mScheduler->schedule([=] {
-            ALOGE("Dumping random sampling of on-screen layers: ");
-            mDrawingState.traverse([&](Layer *layer) {
+            ALOGE("Dumping layer keeping > 20 children alive:");
+            bool leakingParentLayerFound = false;
+            mDrawingState.traverse([&](Layer* layer) {
+                if (leakingParentLayerFound) {
+                    return;
+                }
+                if (layer->getChildrenCount() > 20) {
+                    leakingParentLayerFound = true;
+                    sp<Layer> parent = sp<Layer>::fromExisting(layer);
+                    while (parent) {
+                        ALOGE("Parent Layer: %s%s", parent->getName().c_str(),
+                              (parent->isHandleAlive() ? "handleAlive" : ""));
+                        parent = parent->getParent();
+                    }
+                    // Sample up to 100 layers
+                    ALOGE("Dumping random sampling of child layers total(%zu): ",
+                          layer->getChildrenCount());
+                    int sampleSize = (layer->getChildrenCount() / 100) + 1;
+                    layer->traverseChildren([&](Layer* layer) {
+                        if (rand() % sampleSize == 0) {
+                            ALOGE("Child Layer: %s", layer->getName().c_str());
+                        }
+                    });
+                }
+            });
+
+            int numLayers = 0;
+            mDrawingState.traverse([&](Layer* layer) { numLayers++; });
+
+            ALOGE("Dumping random sampling of on-screen layers total(%u):", numLayers);
+            mDrawingState.traverse([&](Layer* layer) {
                 // Aim to dump about 200 layers to avoid totally trashing
                 // logcat. On the other hand, if there really are 4096 layers
                 // something has gone totally wrong its probably the most
                 // useful information in logcat.
                 if (rand() % 20 == 13) {
-                    ALOGE("Layer: %s", layer->getName().c_str());
+                    ALOGE("Layer: %s%s", layer->getName().c_str(),
+                          (layer->isHandleAlive() ? "handleAlive" : ""));
+                    std::this_thread::sleep_for(std::chrono::milliseconds(5));
                 }
             });
+            ALOGE("Dumping random sampling of off-screen layers total(%zu): ",
+                  mOffscreenLayers.size());
             for (Layer* offscreenLayer : mOffscreenLayers) {
                 if (rand() % 20 == 13) {
-                    ALOGE("Offscreen-layer: %s", offscreenLayer->getName().c_str());
+                    ALOGE("Offscreen-layer: %s%s", offscreenLayer->getName().c_str(),
+                          (offscreenLayer->isHandleAlive() ? "handleAlive" : ""));
+                    std::this_thread::sleep_for(std::chrono::milliseconds(5));
                 }
             }
         }));
@@ -4091,11 +4226,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);
@@ -4131,20 +4270,20 @@
         const TransactionHandler::TransactionFlushState& flushState) {
     using TransactionReadiness = TransactionHandler::TransactionReadiness;
     const auto& transaction = *flushState.transaction;
-    ATRACE_FORMAT("transactionIsReadyToBeApplied vsyncId: %" PRId64,
-                  transaction.frameTimelineInfo.vsyncId);
     TimePoint desiredPresentTime = TimePoint::fromNs(transaction.desiredPresentTime);
     // Do not present if the desiredPresentTime has not passed unless it is more than
     // one second in the future. We ignore timestamps more than 1 second in the future
     // for stability reasons.
     if (!transaction.isAutoTimestamp && desiredPresentTime >= mExpectedPresentTime &&
         desiredPresentTime < mExpectedPresentTime + 1s) {
-        ATRACE_NAME("not current");
+        ATRACE_FORMAT("not current desiredPresentTime: %" PRId64 " expectedPresentTime: %" PRId64,
+                      desiredPresentTime, mExpectedPresentTime);
         return TransactionReadiness::NotReady;
     }
 
     if (!mScheduler->isVsyncValid(mExpectedPresentTime, transaction.originUid)) {
-        ATRACE_NAME("!isVsyncValid");
+        ATRACE_FORMAT("!isVsyncValid expectedPresentTime: %" PRId64 " uid: %d",
+                      mExpectedPresentTime, transaction.originUid);
         return TransactionReadiness::NotReady;
     }
 
@@ -4152,7 +4291,8 @@
     // expected present time of this transaction.
     if (transaction.isAutoTimestamp &&
         frameIsEarly(mExpectedPresentTime, VsyncId{transaction.frameTimelineInfo.vsyncId})) {
-        ATRACE_NAME("frameIsEarly");
+        ATRACE_FORMAT("frameIsEarly vsyncId: %" PRId64 " expectedPresentTime: %" PRId64,
+                      transaction.frameTimelineInfo.vsyncId, mExpectedPresentTime);
         return TransactionReadiness::NotReady;
     }
     return TransactionReadiness::Ready;
@@ -4162,7 +4302,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
@@ -4170,23 +4315,29 @@
             // The current producerId is already a newer producer than the buffer that has a
             // barrier. This means the incoming buffer is older and we can release it here. We
             // don't wait on the barrier since we know that's stale information.
-            if (layer->getDrawingState().producerId > s.bufferData->producerId) {
+            if (layer->getDrawingState().barrierProducerId > 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.
-                ATRACE_NAME("DeleteStaleBuffer");
+                ATRACE_FORMAT("DeleteStaleBuffer %s barrierProducerId:%d > %d",
+                              layer->getDebugName(), layer->getDrawingState().barrierProducerId,
+                              s.bufferData->producerId);
                 return TraverseBuffersReturnValues::DELETE_AND_CONTINUE_TRAVERSAL;
             }
 
-            if (layer->getDrawingState().frameNumber < s.bufferData->barrierFrameNumber) {
+            if (layer->getDrawingState().barrierFrameNumber < s.bufferData->barrierFrameNumber) {
                 const bool willApplyBarrierFrame =
                         flushState.bufferLayersReadyToPresent.contains(s.surface.get()) &&
                         ((flushState.bufferLayersReadyToPresent.get(s.surface.get()) >=
                           s.bufferData->barrierFrameNumber));
                 if (!willApplyBarrierFrame) {
-                    ATRACE_NAME("NotReadyBarrier");
+                    ATRACE_FORMAT("NotReadyBarrier %s barrierFrameNumber:%" PRId64 " > %" PRId64,
+                                  layer->getDebugName(),
+                                  layer->getDrawingState().barrierFrameNumber,
+                                  s.bufferData->barrierFrameNumber);
                     ready = TransactionReadiness::NotReadyBarrier;
                     return TraverseBuffersReturnValues::STOP_TRAVERSAL;
                 }
@@ -4198,25 +4349,28 @@
         const bool hasPendingBuffer =
                 flushState.bufferLayersReadyToPresent.contains(s.surface.get());
         if (layer->backpressureEnabled() && hasPendingBuffer && transaction.isAutoTimestamp) {
-            ATRACE_NAME("hasPendingBuffer");
+            ATRACE_FORMAT("hasPendingBuffer %s", layer->getDebugName());
             ready = TransactionReadiness::NotReady;
             return TraverseBuffersReturnValues::STOP_TRAVERSAL;
         }
 
-        // check fence status
-        const bool allowLatchUnsignaled = shouldLatchUnsignaled(layer, s, transaction.states.size(),
-                                                                flushState.firstTransaction);
-        ATRACE_FORMAT("%s allowLatchUnsignaled=%s", layer->getName().c_str(),
-                      allowLatchUnsignaled ? "true" : "false");
-
-        const bool acquireFenceChanged = s.bufferData &&
+        // ignore the acquire fence if LatchUnsignaledConfig::Always is set.
+        const bool checkAcquireFence = enableLatchUnsignaledConfig != LatchUnsignaledConfig::Always;
+        const bool acquireFenceAvailable = s.bufferData &&
                 s.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged) &&
                 s.bufferData->acquireFence;
-        const bool fenceSignaled =
-                (!acquireFenceChanged ||
-                 s.bufferData->acquireFence->getStatus() != Fence::Status::Unsignaled);
+        const bool fenceSignaled = !checkAcquireFence || !acquireFenceAvailable ||
+                s.bufferData->acquireFence->getStatus() != Fence::Status::Unsignaled;
         if (!fenceSignaled) {
-            if (!allowLatchUnsignaled) {
+            // check fence status
+            const bool allowLatchUnsignaled =
+                    shouldLatchUnsignaled(layer, s, transaction.states.size(),
+                                          flushState.firstTransaction);
+            if (allowLatchUnsignaled) {
+                ATRACE_FORMAT("fence unsignaled try allowLatchUnsignaled %s",
+                              layer->getDebugName());
+                ready = TransactionReadiness::NotReadyUnsignaled;
+            } else {
                 ready = TransactionReadiness::NotReady;
                 auto& listener = s.bufferData->releaseBufferListener;
                 if (listener &&
@@ -4227,16 +4381,12 @@
                                                        "Buffer processing hung up due to stuck "
                                                        "fence. Indicates GPU hang");
                 }
+                ATRACE_FORMAT("fence unsignaled %s", layer->getDebugName());
                 return TraverseBuffersReturnValues::STOP_TRAVERSAL;
             }
-
-            ready = enableLatchUnsignaledConfig == LatchUnsignaledConfig::AutoSingleLayer
-                    ? TransactionReadiness::ReadyUnsignaledSingle
-                    : TransactionReadiness::ReadyUnsignaled;
         }
         return TraverseBuffersReturnValues::CONTINUE_TRAVERSAL;
     });
-    ATRACE_INT("TransactionReadiness", static_cast<int>(ready));
     return ready;
 }
 
@@ -4255,7 +4405,7 @@
 
 bool SurfaceFlinger::applyTransactions(std::vector<TransactionState>& transactions,
                                        VsyncId vsyncId) {
-    Mutex::Autolock _l(mStateLock);
+    Mutex::Autolock lock(mStateLock);
     return applyTransactionsLocked(transactions, vsyncId);
 }
 
@@ -4270,14 +4420,10 @@
                                       transaction.inputWindowCommands,
                                       transaction.desiredPresentTime, transaction.isAutoTimestamp,
                                       std::move(transaction.uncacheBufferIds), transaction.postTime,
-                                      transaction.permissions, transaction.hasListenerCallbacks,
+                                      transaction.hasListenerCallbacks,
                                       transaction.listenerCallbacks, transaction.originPid,
                                       transaction.originUid, transaction.id);
     }
-
-    if (mTransactionTracing) {
-        mTransactionTracing->addCommittedTransactions(transactions, vsyncId.value);
-    }
     return needsTraversal;
 }
 
@@ -4294,10 +4440,8 @@
 
     const auto predictedPresentTime = TimePoint::fromNs(prediction->presentTime);
 
-    // The duration for which SF can delay a frame if it is considered early based on the
-    // VsyncConfig::appWorkDuration.
-    if (constexpr std::chrono::nanoseconds kEarlyLatchMaxThreshold = 100ms;
-        std::chrono::abs(predictedPresentTime - expectedPresentTime) >= kEarlyLatchMaxThreshold) {
+    if (std::chrono::abs(predictedPresentTime - expectedPresentTime) >=
+        scheduler::VsyncConfig::kEarlyLatchMaxThreshold) {
         return false;
     }
 
@@ -4336,9 +4480,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;
         }
@@ -4356,24 +4499,28 @@
 status_t SurfaceFlinger::setTransactionState(
         const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& states,
         const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
-        const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
-        bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffers,
-        bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks,
-        uint64_t transactionId) {
+        InputWindowCommands inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp,
+        const std::vector<client_cache_t>& uncacheBuffers, bool hasListenerCallbacks,
+        const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId,
+        const std::vector<uint64_t>& mergedTransactionIds) {
     ATRACE_CALL();
 
-    uint32_t permissions =
-        callingThreadHasUnscopedSurfaceFlingerAccess() ?
-        layer_state_t::Permission::ACCESS_SURFACE_FLINGER : 0;
-    // Avoid checking for rotation permissions if the caller already has ACCESS_SURFACE_FLINGER
-    // permissions.
-    if ((permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) ||
-        callingThreadHasPermission(sRotateSurfaceFlinger)) {
-        permissions |= layer_state_t::Permission::ROTATE_SURFACE_FLINGER;
+    IPCThreadState* ipc = IPCThreadState::self();
+    const int originPid = ipc->getCallingPid();
+    const int originUid = ipc->getCallingUid();
+    uint32_t permissions = LayerStatePermissions::getTransactionPermissions(originPid, originUid);
+    for (auto composerState : states) {
+        composerState.state.sanitize(permissions);
     }
 
-    if (callingThreadHasPermission(sInternalSystemWindow)) {
-        permissions |= layer_state_t::Permission::INTERNAL_SYSTEM_WINDOW;
+    for (DisplayState display : displays) {
+        display.sanitize(permissions);
+    }
+
+    if (!inputWindowCommands.empty() &&
+        (permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) == 0) {
+        ALOGE("Only privileged callers are allowed to send input commands.");
+        inputWindowCommands.clear();
     }
 
     if (flags & (eEarlyWakeupStart | eEarlyWakeupEnd)) {
@@ -4389,10 +4536,6 @@
 
     const int64_t postTime = systemTime();
 
-    IPCThreadState* ipc = IPCThreadState::self();
-    const int originPid = ipc->getCallingPid();
-    const int originUid = ipc->getCallingUid();
-
     std::vector<uint64_t> uncacheBufferIds;
     uncacheBufferIds.reserve(uncacheBuffers.size());
     for (const auto& uncacheBuffer : uncacheBuffers) {
@@ -4417,6 +4560,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,
@@ -4424,17 +4582,17 @@
                            displays,
                            flags,
                            applyToken,
-                           inputWindowCommands,
+                           std::move(inputWindowCommands),
                            desiredPresentTime,
                            isAutoTimestamp,
                            std::move(uncacheBufferIds),
                            postTime,
-                           permissions,
                            hasListenerCallbacks,
                            listenerCallbacks,
                            originPid,
                            originUid,
-                           transactionId};
+                           transactionId,
+                           mergedTransactionIds};
 
     if (mTransactionTracing) {
         mTransactionTracing->addQueuedTransaction(state);
@@ -4458,14 +4616,12 @@
                                            const InputWindowCommands& inputWindowCommands,
                                            const int64_t desiredPresentTime, bool isAutoTimestamp,
                                            const std::vector<uint64_t>& uncacheBufferIds,
-                                           const int64_t postTime, uint32_t permissions,
-                                           bool hasListenerCallbacks,
+                                           const int64_t postTime, bool hasListenerCallbacks,
                                            const std::vector<ListenerCallbacks>& listenerCallbacks,
                                            int originPid, int originUid, uint64_t transactionId) {
     uint32_t transactionFlags = 0;
     if (!mLayerLifecycleManagerEnabled) {
         for (DisplayState& display : displays) {
-            display.sanitize(permissions);
             transactionFlags |= setDisplayStateLocked(display);
         }
     }
@@ -4482,12 +4638,12 @@
         if (mLegacyFrontEndEnabled) {
             clientStateFlags |=
                     setClientStateLocked(frameTimelineInfo, resolvedState, desiredPresentTime,
-                                         isAutoTimestamp, postTime, permissions, transactionId);
+                                         isAutoTimestamp, postTime, transactionId);
 
         } else /*mLayerLifecycleManagerEnabled*/ {
             clientStateFlags |= updateLayerCallbacksAndStats(frameTimelineInfo, resolvedState,
                                                              desiredPresentTime, isAutoTimestamp,
-                                                             postTime, permissions, transactionId);
+                                                             postTime, transactionId);
         }
         if ((flags & eAnimation) && resolvedState.state.surface) {
             if (const auto layer = LayerHandle::getLayer(resolvedState.state.surface)) {
@@ -4504,12 +4660,7 @@
     }
 
     transactionFlags |= clientStateFlags;
-
-    if (permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) {
-        transactionFlags |= addInputWindowCommands(inputWindowCommands);
-    } else if (!inputWindowCommands.empty()) {
-        ALOGE("Only privileged callers are allowed to send input commands.");
-    }
+    transactionFlags |= addInputWindowCommands(inputWindowCommands);
 
     for (uint64_t uncacheBufferId : uncacheBufferIds) {
         mBufferIdsToUncache.push_back(uncacheBufferId);
@@ -4541,12 +4692,11 @@
 
 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) {
         for (DisplayState& display : transaction.displays) {
-            display.sanitize(transaction.permissions);
             transactionFlags |= setDisplayStateLocked(display);
         }
     }
@@ -4645,10 +4795,8 @@
 uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTimelineInfo,
                                               ResolvedComposerState& composerState,
                                               int64_t desiredPresentTime, bool isAutoTimestamp,
-                                              int64_t postTime, uint32_t permissions,
-                                              uint64_t transactionId) {
+                                              int64_t postTime, uint64_t transactionId) {
     layer_state_t& s = composerState.state;
-    s.sanitize(permissions);
 
     std::vector<ListenerCallbacks> filteredListeners;
     for (auto& listener : s.listeners) {
@@ -4686,6 +4834,7 @@
         }
         return 0;
     }
+    MUTEX_ALIAS(mStateLock, layer->mFlinger->mStateLock);
 
     ui::LayerStack oldLayerStack = layer->getLayerStack(LayerVector::StateSet::Current);
 
@@ -4886,7 +5035,7 @@
         if (layer->setDimmingEnabled(s.dimmingEnabled)) flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eExtendedRangeBrightnessChanged) {
-        if (layer->setExtendedRangeBrightness(s.currentSdrHdrRatio, s.desiredSdrHdrRatio)) {
+        if (layer->setExtendedRangeBrightness(s.currentHdrSdrRatio, s.desiredHdrSdrRatio)) {
             flags |= eTraversalNeeded;
         }
     }
@@ -4975,7 +5124,8 @@
     }
 
     if (layer->setTransactionCompletedListeners(callbackHandles,
-                                                layer->willPresentCurrentTransaction())) {
+                                                layer->willPresentCurrentTransaction() ||
+                                                        layer->willReleaseBufferOnLatch())) {
         flags |= eTraversalNeeded;
     }
 
@@ -4995,10 +5145,8 @@
                                                       ResolvedComposerState& composerState,
                                                       int64_t desiredPresentTime,
                                                       bool isAutoTimestamp, int64_t postTime,
-                                                      uint32_t permissions,
                                                       uint64_t transactionId) {
     layer_state_t& s = composerState.state;
-    s.sanitize(permissions);
 
     std::vector<ListenerCallbacks> filteredListeners;
     for (auto& listener : s.listeners) {
@@ -5061,6 +5209,12 @@
         if (layer->setSidebandStream(s.sidebandStream)) flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eBufferChanged) {
+        std::optional<ui::Transform::RotationFlags> transformHint = std::nullopt;
+        frontend::LayerSnapshot* snapshot = mLayerSnapshotBuilder.getSnapshot(layer->sequence);
+        if (snapshot) {
+            transformHint = snapshot->transformHint;
+        }
+        layer->setTransformHint(transformHint);
         if (layer->setBuffer(composerState.externalTexture, *s.bufferData, postTime,
                              desiredPresentTime, isAutoTimestamp, dequeueBufferTimestamp,
                              frameTimelineInfo)) {
@@ -5083,8 +5237,9 @@
     }
 
     const auto& requestedLayerState = mLayerLifecycleManager.getLayerFromId(layer->getSequence());
-    bool willPresentCurrentTransaction =
-            requestedLayerState && requestedLayerState->hasReadyFrame();
+    bool willPresentCurrentTransaction = requestedLayerState &&
+            (requestedLayerState->hasReadyFrame() ||
+             requestedLayerState->willReleaseBufferOnLatch());
     if (layer->setTransactionCompletedListeners(callbackHandles, willPresentCurrentTransaction))
         flags |= eTraversalNeeded;
 
@@ -5105,7 +5260,7 @@
 
     sp<Layer> mirrorLayer;
     sp<Layer> mirrorFrom;
-    LayerCreationArgs mirrorArgs(args);
+    LayerCreationArgs mirrorArgs = LayerCreationArgs::fromOtherArgs(args);
     {
         Mutex::Autolock _l(mStateLock);
         mirrorFrom = LayerHandle::getLayer(mirrorFromHandle);
@@ -5120,16 +5275,11 @@
             return result;
         }
 
-        mirrorLayer->setClonedChild(mirrorFrom->createClone());
+        mirrorLayer->setClonedChild(mirrorFrom->createClone(mirrorLayer->getSequence()));
     }
 
     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 */);
 }
@@ -5156,7 +5306,7 @@
         }
 
         layerStack = display->getLayerStack();
-        LayerCreationArgs mirrorArgs(args);
+        LayerCreationArgs mirrorArgs = LayerCreationArgs::fromOtherArgs(args);
         mirrorArgs.flags |= ISurfaceComposerClient::eNoColorFill;
         mirrorArgs.addToRoot = true;
         mirrorArgs.layerStackToMirror = layerStack;
@@ -5171,11 +5321,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);
@@ -5223,12 +5368,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) {
@@ -5270,17 +5409,15 @@
 
     Mutex::Autolock lock(mStateLock);
     markLayerPendingRemovalLocked(layer);
+    layer->onHandleDestroyed();
     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();
@@ -5288,13 +5425,12 @@
 
     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
@@ -5314,21 +5450,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) {
@@ -5372,14 +5503,19 @@
             onActiveDisplayChangedLocked(activeDisplay.get(), *display);
         }
 
-        // Keep uclamp in a separate syscall and set it before changing to RT due to b/190237315.
-        // We can merge the syscall later.
-        if (SurfaceFlinger::setSchedAttr(true) != NO_ERROR) {
-            ALOGW("Couldn't set uclamp.min on display on: %s\n", strerror(errno));
+        if (displayId == mActiveDisplayId) {
+            // TODO(b/281692563): Merge the syscalls. For now, keep uclamp in a separate syscall and
+            // set it before SCHED_FIFO due to b/190237315.
+            if (setSchedAttr(true) != NO_ERROR) {
+                ALOGW("Failed to set uclamp.min after powering on active display: %s",
+                      strerror(errno));
+            }
+            if (setSchedFifo(true) != NO_ERROR) {
+                ALOGW("Failed to set SCHED_FIFO after powering on active display: %s",
+                      strerror(errno));
+            }
         }
-        if (SurfaceFlinger::setSchedFifo(true) != NO_ERROR) {
-            ALOGW("Couldn't set SCHED_FIFO on display on: %s\n", strerror(errno));
-        }
+
         getHwComposer().setPowerMode(displayId, mode);
         if (displayId == mActiveDisplayId && mode != hal::PowerMode::DOZE_SUSPEND) {
             setHWCVsyncEnabled(displayId,
@@ -5393,15 +5529,21 @@
         scheduleComposite(FrameHint::kActive);
     } else if (mode == hal::PowerMode::OFF) {
         // Turn off the display
-        if (SurfaceFlinger::setSchedFifo(false) != NO_ERROR) {
-            ALOGW("Couldn't set SCHED_OTHER on display off: %s\n", strerror(errno));
-        }
-        if (SurfaceFlinger::setSchedAttr(false) != NO_ERROR) {
-            ALOGW("Couldn't set uclamp.min on display off: %s\n", strerror(errno));
-        }
-        if (displayId == mActiveDisplayId && *currentModeOpt != hal::PowerMode::DOZE_SUSPEND) {
-            mScheduler->disableHardwareVsync(displayId, true);
-            mScheduler->enableSyntheticVsync();
+
+        if (displayId == mActiveDisplayId) {
+            if (setSchedFifo(false) != NO_ERROR) {
+                ALOGW("Failed to set SCHED_OTHER after powering off active display: %s",
+                      strerror(errno));
+            }
+            if (setSchedAttr(false) != NO_ERROR) {
+                ALOGW("Failed set uclamp.min after powering off active display: %s",
+                      strerror(errno));
+            }
+
+            if (*currentModeOpt != hal::PowerMode::DOZE_SUSPEND) {
+                mScheduler->disableHardwareVsync(displayId, true);
+                mScheduler->enableSyntheticVsync();
+            }
         }
 
         // Make sure HWVsync is disabled before turning off the display
@@ -5530,7 +5672,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());
@@ -5769,22 +5912,15 @@
         return layersProto;
     }
 
-    const frontend::LayerHierarchy& root = mLayerHierarchyBuilder.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, mLayerSnapshotBuilder,
-                                                mLegacyLayers, traceFlags);
-    }
-    return layersProto;
+    return LayerProtoFromSnapshotGenerator(mLayerSnapshotBuilder, mFrontEndDisplayInfos,
+                                           mLegacyLayers, traceFlags)
+            .generate(mLayerHierarchyBuilder.getHierarchy());
 }
 
-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);
@@ -5797,6 +5933,7 @@
                                                 displayProto->mutable_transform());
         displayProto->set_is_virtual(display->isVirtual());
     }
+    return displays;
 }
 
 void SurfaceFlinger::dumpHwc(std::string& result) const {
@@ -6010,6 +6147,29 @@
 
     result.append(mTimeStats->miniDump());
     result.append("\n");
+
+    result.append("Window Infos:\n");
+    StringAppendF(&result, "  input flinger update vsync id: %" PRId64 "\n",
+                  mLastInputFlingerUpdateVsyncId.value);
+    StringAppendF(&result, "  input flinger update timestamp (ns): %" PRId64 "\n",
+                  mLastInputFlingerUpdateTimestamp);
+    result.append("\n");
+
+    if (int64_t unsentVsyncId = mWindowInfosListenerInvoker->getUnsentMessageVsyncId().value;
+        unsentVsyncId != -1) {
+        StringAppendF(&result, "  unsent input flinger update vsync id: %" PRId64 "\n",
+                      unsentVsyncId);
+        StringAppendF(&result, "  unsent input flinger update timestamp (ns): %" PRId64 "\n",
+                      mWindowInfosListenerInvoker->getUnsentMessageTimestamp());
+        result.append("\n");
+    }
+
+    if (uint32_t pendingMessages = mWindowInfosListenerInvoker->getPendingMessageCount();
+        pendingMessages != 0) {
+        StringAppendF(&result, "  pending input flinger calls: %" PRIu32 "\n",
+                      mWindowInfosListenerInvoker->getPendingMessageCount());
+        result.append("\n");
+    }
 }
 
 mat4 SurfaceFlinger::calculateColorMatrix(float saturation) {
@@ -6327,9 +6487,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();
                     }
@@ -6819,6 +6980,32 @@
     return NO_ERROR;
 }
 
+namespace {
+
+ui::Dataspace pickBestDataspace(ui::Dataspace requestedDataspace, const DisplayDevice* display,
+                                bool capturingHdrLayers, bool hintForSeamlessTransition) {
+    if (requestedDataspace != ui::Dataspace::UNKNOWN || display == nullptr) {
+        return requestedDataspace;
+    }
+
+    const auto& state = display->getCompositionDisplay()->getState();
+
+    const auto dataspaceForColorMode = ui::pickDataspaceFor(state.colorMode);
+
+    if (capturingHdrLayers && !hintForSeamlessTransition) {
+        // For now since we only support 8-bit screenshots, just use HLG and
+        // assume that 1.0 >= display max luminance. This isn't quite as future
+        // proof as PQ is, but is good enough.
+        // Consider using PQ once we support 16-bit screenshots and we're able
+        // to consistently supply metadata to image encoders.
+        return ui::Dataspace::BT2020_HLG;
+    }
+
+    return dataspaceForColorMode;
+}
+
+} // namespace
+
 status_t SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args,
                                         const sp<IScreenCaptureListener>& captureListener) {
     ATRACE_CALL();
@@ -6833,7 +7020,7 @@
     wp<const DisplayDevice> displayWeak;
     ui::LayerStack layerStack;
     ui::Size reqSize(args.width, args.height);
-    ui::Dataspace dataspace;
+    std::unordered_set<uint32_t> excludeLayerIds;
     {
         Mutex::Autolock lock(mStateLock);
         sp<DisplayDevice> display = getDisplayDeviceLocked(args.displayToken);
@@ -6846,25 +7033,31 @@
             reqSize = display->getLayerStackSpaceRect().getSize();
         }
 
-        // Allow the caller to specify a dataspace regardless of the display's color mode, e.g. if
-        // it wants sRGB regardless of the display's wide color mode.
-        dataspace = args.dataspace == ui::Dataspace::UNKNOWN
-                ? ui::pickDataspaceFor(display->getCompositionDisplay()->getState().colorMode)
-                : args.dataspace;
+        for (const auto& handle : args.excludeHandles) {
+            uint32_t excludeLayer = LayerHandle::getLayerId(handle);
+            if (excludeLayer != UNASSIGNED_LAYER_ID) {
+                excludeLayerIds.emplace(excludeLayer);
+            } else {
+                ALOGW("Invalid layer handle passed as excludeLayer to captureDisplay");
+                return NAME_NOT_FOUND;
+            }
+        }
     }
 
     RenderAreaFuture renderAreaFuture = ftl::defer([=] {
-        return DisplayRenderArea::create(displayWeak, args.sourceCrop, reqSize, dataspace,
-                                         args.useIdentityTransform, args.captureSecureLayers);
+        return DisplayRenderArea::create(displayWeak, args.sourceCrop, reqSize,
+                                         ui::Dataspace::UNKNOWN, args.useIdentityTransform,
+                                         args.hintForSeamlessTransition, args.captureSecureLayers);
     });
 
     GetLayerSnapshotsFunction getLayerSnapshots;
     if (mLayerLifecycleManagerEnabled) {
         getLayerSnapshots =
-                getLayerSnapshotsForScreenshots(layerStack, args.uid, /*snapshotFilterFn=*/nullptr);
+                getLayerSnapshotsForScreenshots(layerStack, args.uid, std::move(excludeLayerIds));
     } else {
-        auto traverseLayers = [this, args, layerStack](const LayerVector::Visitor& visitor) {
-            traverseLayersInLayerStack(layerStack, args.uid, visitor);
+        auto traverseLayers = [this, args, excludeLayerIds,
+                               layerStack](const LayerVector::Visitor& visitor) {
+            traverseLayersInLayerStack(layerStack, args.uid, std::move(excludeLayerIds), visitor);
         };
         getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
     }
@@ -6880,7 +7073,6 @@
     ui::LayerStack layerStack;
     wp<const DisplayDevice> displayWeak;
     ui::Size size;
-    ui::Dataspace dataspace;
     {
         Mutex::Autolock lock(mStateLock);
 
@@ -6892,12 +7084,12 @@
         displayWeak = display;
         layerStack = display->getLayerStack();
         size = display->getLayerStackSpaceRect().getSize();
-        dataspace = ui::pickDataspaceFor(display->getCompositionDisplay()->getState().colorMode);
     }
 
     RenderAreaFuture renderAreaFuture = ftl::defer([=] {
-        return DisplayRenderArea::create(displayWeak, Rect(), size, dataspace,
+        return DisplayRenderArea::create(displayWeak, Rect(), size, ui::Dataspace::UNKNOWN,
                                          false /* useIdentityTransform */,
+                                         false /* hintForSeamlessTransition */,
                                          false /* captureSecureLayers */);
     });
 
@@ -6907,7 +7099,7 @@
                                                             /*snapshotFilterFn=*/nullptr);
     } else {
         auto traverseLayers = [this, layerStack](const LayerVector::Visitor& visitor) {
-            traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, visitor);
+            traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, {}, visitor);
         };
         getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
     }
@@ -6939,7 +7131,7 @@
     sp<Layer> parent;
     Rect crop(args.sourceCrop);
     std::unordered_set<uint32_t> excludeLayerIds;
-    ui::Dataspace dataspace;
+    ui::Dataspace dataspace = args.dataspace;
 
     // Call this before holding mStateLock to avoid any deadlocking.
     bool canCaptureBlackoutContent = hasCaptureBlackoutContentPermission();
@@ -6986,12 +7178,6 @@
                 return NAME_NOT_FOUND;
             }
         }
-
-        // The dataspace is depended on the color mode of display, that could use non-native mode
-        // (ex. displayP3) to enhance the content, but some cases are checking native RGB in bytes,
-        // and failed if display is not in native mode. This provide a way to force using native
-        // colors when capture.
-        dataspace = args.dataspace;
     } // mStateLock
 
     // really small crop or frameScale
@@ -7002,13 +7188,35 @@
 
     bool childrenOnly = args.childrenOnly;
     RenderAreaFuture renderAreaFuture = ftl::defer([=]() -> std::unique_ptr<RenderArea> {
+        ui::Transform layerTransform;
+        Rect layerBufferSize;
+        if (mLayerLifecycleManagerEnabled) {
+            frontend::LayerSnapshot* snapshot =
+                    mLayerSnapshotBuilder.getSnapshot(parent->getSequence());
+            if (!snapshot) {
+                ALOGW("Couldn't find layer snapshot for %d", parent->getSequence());
+            } else {
+                layerTransform = snapshot->localTransform;
+                layerBufferSize = snapshot->bufferSize;
+            }
+        } else {
+            layerTransform = parent->getTransform();
+            layerBufferSize = parent->getBufferSize(parent->getDrawingState());
+        }
+
         return std::make_unique<LayerRenderArea>(*this, parent, crop, reqSize, dataspace,
-                                                 childrenOnly, args.captureSecureLayers);
+                                                 childrenOnly, args.captureSecureLayers,
+                                                 layerTransform, layerBufferSize,
+                                                 args.hintForSeamlessTransition);
     });
     GetLayerSnapshotsFunction getLayerSnapshots;
     if (mLayerLifecycleManagerEnabled) {
-        FloatRect parentCrop = crop.isEmpty() ? FloatRect(0, 0, reqSize.width, reqSize.height)
-                                              : crop.toFloatRect();
+        std::optional<FloatRect> parentCrop = std::nullopt;
+        if (args.childrenOnly) {
+            parentCrop = crop.isEmpty() ? FloatRect(0, 0, reqSize.width, reqSize.height)
+                                        : crop.toFloatRect();
+        }
+
         getLayerSnapshots = getLayerSnapshotsForScreenshots(parent->sequence, args.uid,
                                                             std::move(excludeLayerIds),
                                                             args.childrenOnly, parentCrop);
@@ -7185,29 +7393,48 @@
         return ftl::yield<FenceResult>(base::unexpected(PERMISSION_DENIED)).share();
     }
 
-    captureResults.buffer = buffer->getBuffer();
-    auto dataspace = renderArea->getReqDataSpace();
+    auto capturedBuffer = buffer;
+
+    auto requestedDataspace = renderArea->getReqDataSpace();
     auto parent = renderArea->getParentLayer();
     auto renderIntent = RenderIntent::TONE_MAP_COLORIMETRIC;
     auto sdrWhitePointNits = DisplayDevice::sDefaultMaxLumiance;
     auto displayBrightnessNits = DisplayDevice::sDefaultMaxLumiance;
 
-    if (dataspace == ui::Dataspace::UNKNOWN && parent) {
+    captureResults.capturedDataspace = requestedDataspace;
+
+    {
         Mutex::Autolock lock(mStateLock);
-        auto display = findDisplay([layerStack = parent->getLayerStack()](const auto& display) {
-            return display.getLayerStack() == layerStack;
-        });
-        if (!display) {
-            // If the layer is not on a display, use the dataspace for the default display.
-            display = getDefaultDisplayDeviceLocked();
+        const DisplayDevice* display = nullptr;
+        if (parent) {
+            display = findDisplay([layerStack = parent->getLayerStack()](const auto& display) {
+                          return display.getLayerStack() == layerStack;
+                      }).get();
         }
 
-        dataspace = ui::pickDataspaceFor(display->getCompositionDisplay()->getState().colorMode);
-        renderIntent = display->getCompositionDisplay()->getState().renderIntent;
-        sdrWhitePointNits = display->getCompositionDisplay()->getState().sdrWhitePointNits;
-        displayBrightnessNits = display->getCompositionDisplay()->getState().displayBrightnessNits;
+        if (display == nullptr) {
+            display = renderArea->getDisplayDevice().get();
+        }
+
+        if (display == nullptr) {
+            display = getDefaultDisplayDeviceLocked().get();
+        }
+
+        if (display != nullptr) {
+            const auto& state = display->getCompositionDisplay()->getState();
+            captureResults.capturedDataspace =
+                    pickBestDataspace(requestedDataspace, display, captureResults.capturedHdrLayers,
+                                      renderArea->getHintForSeamlessTransition());
+            sdrWhitePointNits = state.sdrWhitePointNits;
+            displayBrightnessNits = state.displayBrightnessNits;
+
+            if (requestedDataspace == ui::Dataspace::UNKNOWN) {
+                renderIntent = state.renderIntent;
+            }
+        }
     }
-    captureResults.capturedDataspace = dataspace;
+
+    captureResults.buffer = capturedBuffer->getBuffer();
 
     ui::LayerStack layerStack{ui::DEFAULT_LAYER_STACK};
     if (!layers.empty()) {
@@ -7224,9 +7451,9 @@
         return layerFEs;
     };
 
-    auto present = [this, buffer = std::move(buffer), dataspace, sdrWhitePointNits,
-                    displayBrightnessNits, grayscale, layerFEs = copyLayerFEs(), layerStack,
-                    regionSampling, renderArea = std::move(renderArea),
+    auto present = [this, buffer = capturedBuffer, dataspace = captureResults.capturedDataspace,
+                    sdrWhitePointNits, displayBrightnessNits, grayscale, layerFEs = copyLayerFEs(),
+                    layerStack, regionSampling, renderArea = std::move(renderArea),
                     renderIntent]() -> FenceResult {
         std::unique_ptr<compositionengine::CompositionEngine> compositionEngine =
                 mFactory.createCompositionEngine();
@@ -7235,6 +7462,16 @@
         compositionengine::Output::ColorProfile colorProfile{.dataspace = dataspace,
                                                              .renderIntent = renderIntent};
 
+        float targetBrightness = 1.0f;
+        if (dataspace == ui::Dataspace::BT2020_HLG) {
+            const float maxBrightnessNits = displayBrightnessNits / sdrWhitePointNits * 203;
+            // With a low dimming ratio, don't fit the entire curve. Otherwise mixed content
+            // will appear way too bright.
+            if (maxBrightnessNits < 1000.f) {
+                targetBrightness = 1000.f / maxBrightnessNits;
+            }
+        }
+
         std::shared_ptr<ScreenCaptureOutput> output = createScreenCaptureOutput(
                 ScreenCaptureOutputArgs{.compositionEngine = *compositionEngine,
                                         .colorProfile = colorProfile,
@@ -7243,6 +7480,7 @@
                                         .buffer = std::move(buffer),
                                         .sdrWhitePointNits = sdrWhitePointNits,
                                         .displayBrightnessNits = displayBrightnessNits,
+                                        .targetBrightness = targetBrightness,
                                         .regionSampling = regionSampling});
 
         const float colorSaturation = grayscale ? 0 : 1;
@@ -7274,12 +7512,14 @@
                                                 : ftl::yield(present()).share();
 
     for (auto& [layer, layerFE] : layers) {
-        layer->onLayerDisplayed(
-                ftl::Future(presentFuture)
-                        .then([layerFE = std::move(layerFE)](FenceResult) {
-                            return layerFE->stealCompositionResult().releaseFences.back().get();
-                        })
-                        .share());
+        layer->onLayerDisplayed(ftl::Future(presentFuture)
+                                        .then([layerFE = std::move(layerFE)](FenceResult) {
+                                            return layerFE->stealCompositionResult()
+                                                    .releaseFences.back()
+                                                    .first.get();
+                                        })
+                                        .share(),
+                                ui::INVALID_LAYER_STACK);
     }
 
     return presentFuture;
@@ -7310,6 +7550,7 @@
 }
 
 void SurfaceFlinger::traverseLayersInLayerStack(ui::LayerStack layerStack, const int32_t uid,
+                                                std::unordered_set<uint32_t> excludeLayerIds,
                                                 const LayerVector::Visitor& visitor) {
     // We loop through the first level of layers without traversing,
     // as we need to determine which layers belong to the requested display.
@@ -7328,6 +7569,17 @@
             if (uid != CaptureArgs::UNSET_UID && layer->getOwnerUid() != uid) {
                 return;
             }
+
+            if (!excludeLayerIds.empty()) {
+                auto p = sp<Layer>::fromExisting(layer);
+                while (p != nullptr) {
+                    if (excludeLayerIds.count(p->sequence) != 0) {
+                        return;
+                    }
+                    p = p->getParent();
+                }
+            }
+
             visitor(layer);
         });
     }
@@ -7656,6 +7908,7 @@
         ALOGD("Layer was destroyed soon after creation %p", state.layer.unsafe_get());
         return;
     }
+    MUTEX_ALIAS(mStateLock, layer->mFlinger->mStateLock);
 
     sp<Layer> parent;
     bool addToRoot = state.addToRoot;
@@ -7692,10 +7945,6 @@
     if (hintDisplay) {
         layer->updateTransformHint(hintDisplay->getTransformHint());
     }
-
-    if (mTransactionTracing) {
-        mTransactionTracing->onLayerAddedToDrawingState(layer->getSequence(), vsyncId.value);
-    }
 }
 
 void SurfaceFlinger::sample() {
@@ -7715,26 +7964,33 @@
                                                   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->setPacesetterDisplay(mActiveDisplayId);
 
     onActiveDisplaySizeChanged(activeDisplay);
     mActiveDisplayTransformHint = activeDisplay.getTransformHint();
+    sActiveDisplayRotationFlags = ui::Transform::toRotationFlags(activeDisplay.getOrientation());
 
     // The policy of the new active/pacesetter display may have changed while it was inactive. In
     // that case, its preferred mode has not been propagated to HWC (via setDesiredActiveMode). In
     // either case, the Scheduler's cachedModeChangedParams must be initialized to the newly active
     // mode, and the kernel idle timer of the newly active display must be toggled.
-    constexpr bool kForce = true;
-    applyRefreshRateSelectorPolicy(mActiveDisplayId, activeDisplay.refreshRateSelector(), kForce);
+    applyRefreshRateSelectorPolicy(mActiveDisplayId, activeDisplay.refreshRateSelector(),
+                                   forceApplyPolicy);
 }
 
 status_t SurfaceFlinger::addWindowInfosListener(
@@ -7828,13 +8084,15 @@
                                          ISurfaceComposerClient::eNoColorFill,
                                          gui::LayerMetadata());
             sp<Layer> childMirror;
-            createEffectLayer(mirrorArgs, &unused, &childMirror);
-            childMirror->setClonedChild(layer->createClone());
-            if (mTransactionTracing) {
-                mTransactionTracing->onLayerAddedToDrawingState(childMirror->getSequence(),
-                                                                vsyncId.value);
+            {
+                Mutex::Autolock lock(mStateLock);
+                createEffectLayer(mirrorArgs, &unused, &childMirror);
+                MUTEX_ALIAS(mStateLock, childMirror->mFlinger->mStateLock);
+                childMirror->setClonedChild(layer->createClone(childMirror->getSequence()));
+                childMirror->reparent(mirrorDisplay.rootHandle);
             }
-            childMirror->reparent(mirrorDisplay.rootHandle);
+            // lock on mStateLock needs to be released before binder handle gets destroyed
+            unused.clear();
         }
     }
     return true;
@@ -7927,7 +8185,7 @@
                 });
     }
     if (mLegacyFrontEndEnabled && !mLayerLifecycleManagerEnabled) {
-        mDrawingState.traverseInZOrder([&refreshArgs, cursorOnly, &layers](Layer* layer) {
+        auto moveSnapshots = [&layers, &refreshArgs, cursorOnly](Layer* layer) {
             if (const auto& layerFE = layer->getCompositionEngineLayerFE()) {
                 if (cursorOnly &&
                     layer->getLayerSnapshot()->compositionType !=
@@ -7938,7 +8196,22 @@
                 refreshArgs.layers.push_back(layerFE);
                 layers.emplace_back(layer, layerFE.get());
             }
-        });
+        };
+
+        if (cursorOnly || !mVisibleRegionsDirty) {
+            // for hot path avoid traversals by walking though the previous composition list
+            for (sp<Layer> layer : mPreviouslyComposedLayers) {
+                moveSnapshots(layer.get());
+            }
+        } else {
+            mPreviouslyComposedLayers.clear();
+            mDrawingState.traverseInZOrder(
+                    [&moveSnapshots](Layer* layer) { moveSnapshots(layer); });
+            mPreviouslyComposedLayers.reserve(layers.size());
+            for (auto [layer, _] : layers) {
+                mPreviouslyComposedLayers.push_back(sp<Layer>::fromExisting(layer));
+            }
+        }
     }
 
     return layers;
@@ -7960,7 +8233,7 @@
                     if (layerStack && snapshot->outputFilter.layerStack != *layerStack) {
                         return;
                     }
-                    if (uid != CaptureArgs::UNSET_UID && snapshot->inputInfo.ownerUid != uid) {
+                    if (uid != CaptureArgs::UNSET_UID && snapshot->uid != uid) {
                         return;
                     }
                     if (!snapshot->hasSomethingToDraw()) {
@@ -7985,9 +8258,48 @@
 }
 
 std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()>
+SurfaceFlinger::getLayerSnapshotsForScreenshots(std::optional<ui::LayerStack> layerStack,
+                                                uint32_t uid,
+                                                std::unordered_set<uint32_t> excludeLayerIds) {
+    return [&, layerStack, uid, excludeLayerIds = std::move(excludeLayerIds)]() {
+        if (excludeLayerIds.empty()) {
+            auto getLayerSnapshotsFn =
+                    getLayerSnapshotsForScreenshots(layerStack, uid, /*snapshotFilterFn=*/nullptr);
+            std::vector<std::pair<Layer*, sp<LayerFE>>> layers = getLayerSnapshotsFn();
+            return layers;
+        }
+
+        frontend::LayerSnapshotBuilder::Args
+                args{.root = mLayerHierarchyBuilder.getHierarchy(),
+                     .layerLifecycleManager = mLayerLifecycleManager,
+                     .forceUpdate = frontend::LayerSnapshotBuilder::ForceUpdateFlags::HIERARCHY,
+                     .displays = mFrontEndDisplayInfos,
+                     .displayChanges = true,
+                     .globalShadowSettings = mDrawingState.globalShadowSettings,
+                     .supportsBlur = mSupportsBlur,
+                     .forceFullDamage = mForceFullDamage,
+                     .excludeLayerIds = std::move(excludeLayerIds),
+                     .supportedLayerGenericMetadata =
+                             getHwComposer().getSupportedLayerGenericMetadata(),
+                     .genericLayerMetadataKeyMap = getGenericLayerMetadataKeyMap()};
+        mLayerSnapshotBuilder.update(args);
+
+        auto getLayerSnapshotsFn =
+                getLayerSnapshotsForScreenshots(layerStack, uid, /*snapshotFilterFn=*/nullptr);
+        std::vector<std::pair<Layer*, sp<LayerFE>>> layers = getLayerSnapshotsFn();
+
+        args.excludeLayerIds.clear();
+        mLayerSnapshotBuilder.update(args);
+
+        return layers;
+    };
+}
+
+std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()>
 SurfaceFlinger::getLayerSnapshotsForScreenshots(uint32_t rootLayerId, uint32_t uid,
                                                 std::unordered_set<uint32_t> excludeLayerIds,
-                                                bool childrenOnly, const FloatRect& parentCrop) {
+                                                bool childrenOnly,
+                                                const std::optional<FloatRect>& parentCrop) {
     return [&, rootLayerId, uid, excludeLayerIds = std::move(excludeLayerIds), childrenOnly,
             parentCrop]() {
         auto root = mLayerHierarchyBuilder.getPartialHierarchy(rootLayerId, childrenOnly);
@@ -8000,7 +8312,7 @@
                      .globalShadowSettings = mDrawingState.globalShadowSettings,
                      .supportsBlur = mSupportsBlur,
                      .forceFullDamage = mForceFullDamage,
-                     .parentCrop = {parentCrop},
+                     .parentCrop = parentCrop,
                      .excludeLayerIds = std::move(excludeLayerIds),
                      .supportedLayerGenericMetadata =
                              getHwComposer().getSupportedLayerGenericMetadata(),
@@ -8018,8 +8330,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,
@@ -8036,12 +8348,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() {
@@ -8815,6 +9143,15 @@
     static_cast<void>(mScheduler->scheduleDelayed([&]() { scheduleRepaint(); }, ms2ns(delayInMs)));
 }
 
+const DisplayDevice* SurfaceFlinger::getDisplayFromLayerStack(ui::LayerStack layerStack) {
+    for (const auto& [_, display] : mDisplays) {
+        if (display->getLayerStack() == layerStack) {
+            return display.get();
+        }
+    }
+    return nullptr;
+}
+
 } // namespace android
 
 #if defined(__gl_h_)
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 338531f..e2691ab 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -28,6 +28,7 @@
 #include <android/gui/ISurfaceComposerClient.h>
 #include <cutils/atomic.h>
 #include <cutils/compiler.h>
+#include <ftl/algorithm.h>
 #include <ftl/future.h>
 #include <ftl/non_null.h>
 #include <gui/BufferQueue.h>
@@ -174,10 +175,15 @@
     // Latch unsignaled is permitted when a single layer is updated in a frame,
     // and the update includes just a buffer update (i.e. no sync transactions
     // or geometry changes).
+    // Latch unsignaled is also only permitted when a single transaction is ready
+    // to be applied. If we pass an unsignaled fence to HWC, HWC might miss presenting
+    // the frame if the fence does not fire in time. If we apply another transaction,
+    // we may penalize the other transaction unfairly.
     AutoSingleLayer,
 
     // All buffers are latched unsignaled. This behaviour is discouraged as it
     // can break sync transactions, stall the display and cause undesired side effects.
+    // This is equivalent to ignoring the acquire fence when applying transactions.
     Always,
 };
 
@@ -290,8 +296,7 @@
     // the client can no longer modify this layer directly.
     void onHandleDestroyed(BBinder* handle, sp<Layer>& layer, uint32_t layerId);
 
-    // TODO: Remove atomic if move dtor to main thread CL lands
-    std::atomic<uint32_t> mNumClones;
+    std::vector<Layer*> mLayerMirrorRoots;
 
     TransactionCallbackInvoker& getTransactionCallbackInvoker() {
         return mTransactionCallbackInvoker;
@@ -323,6 +328,16 @@
     bool mIgnoreHwcPhysicalDisplayOrientation = false;
 
     void forceFutureUpdate(int delayInMs);
+    const DisplayDevice* getDisplayFromLayerStack(ui::LayerStack)
+            REQUIRES(mStateLock, kMainThreadContext);
+
+    // TODO (b/259407931): Remove.
+    // TODO (b/281857977): This should be annotated with REQUIRES(kMainThreadContext), but this
+    // would require thread safety annotations throughout the frontend (in particular Layer and
+    // LayerFE).
+    static ui::Transform::RotationFlags getActiveDisplayRotationFlags() {
+        return sActiveDisplayRotationFlags;
+    }
 
 protected:
     // We're reference counted, never destroy SurfaceFlinger directly
@@ -455,26 +470,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;
@@ -512,7 +507,7 @@
 
     // Implements ISurfaceComposer
     sp<IBinder> createDisplay(const String8& displayName, bool secure,
-                              float requestedRefreshRate = 0);
+                              float requestedRefreshRate = 0.0f);
     void destroyDisplay(const sp<IBinder>& displayToken);
     std::vector<PhysicalDisplayId> getPhysicalDisplayIds() const EXCLUDES(mStateLock) {
         Mutex::Autolock lock(mStateLock);
@@ -520,15 +515,13 @@
     }
 
     sp<IBinder> getPhysicalDisplayToken(PhysicalDisplayId displayId) const;
-    status_t setTransactionState(const FrameTimelineInfo& frameTimelineInfo,
-                                 Vector<ComposerState>& state, const Vector<DisplayState>& displays,
-                                 uint32_t flags, const sp<IBinder>& applyToken,
-                                 const InputWindowCommands& inputWindowCommands,
-                                 int64_t desiredPresentTime, bool isAutoTimestamp,
-                                 const std::vector<client_cache_t>& uncacheBuffers,
-                                 bool hasListenerCallbacks,
-                                 const std::vector<ListenerCallbacks>& listenerCallbacks,
-                                 uint64_t transactionId) override;
+    status_t setTransactionState(
+            const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
+            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            InputWindowCommands inputWindowCommands, int64_t desiredPresentTime,
+            bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffers,
+            bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks,
+            uint64_t transactionId, const std::vector<uint64_t>& mergedTransactionIds) override;
     void bootFinished();
     virtual status_t getSupportedFrameTimestamps(std::vector<FrameEvent>* outSupported) const;
     sp<IDisplayEventConnection> createDisplayEventConnection(
@@ -721,15 +714,15 @@
             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);
     void updateLayerHistory(const frontend::LayerSnapshot& snapshot);
-    LifecycleUpdate flushLifecycleUpdates() REQUIRES(kMainThreadContext);
+    frontend::Update flushLifecycleUpdates() REQUIRES(kMainThreadContext);
 
-    void updateInputFlinger();
+    void updateInputFlinger(VsyncId);
     void persistDisplayBrightness(bool needsComposite) REQUIRES(kMainThreadContext);
     void buildWindowInfos(std::vector<gui::WindowInfo>& outWindowInfos,
                           std::vector<gui::DisplayInfo>& outDisplayInfos);
@@ -737,19 +730,23 @@
     void updateCursorAsync();
 
     void initScheduler(const sp<const DisplayDevice>&) REQUIRES(kMainThreadContext, mStateLock);
+
+    void resetPhaseConfiguration(Fps) REQUIRES(mStateLock, kMainThreadContext);
     void updatePhaseConfiguration(Fps) REQUIRES(mStateLock);
 
     /*
      * Transactions
      */
-    bool applyTransactionState(
-            const FrameTimelineInfo& info, std::vector<ResolvedComposerState>& state,
-            Vector<DisplayState>& displays, uint32_t flags,
-            const InputWindowCommands& inputWindowCommands, const int64_t desiredPresentTime,
-            bool isAutoTimestamp, const std::vector<uint64_t>& uncacheBufferIds,
-            const int64_t postTime, uint32_t permissions, bool hasListenerCallbacks,
-            const std::vector<ListenerCallbacks>& listenerCallbacks, int originPid, int originUid,
-            uint64_t transactionId) REQUIRES(mStateLock);
+    bool applyTransactionState(const FrameTimelineInfo& info,
+                               std::vector<ResolvedComposerState>& state,
+                               Vector<DisplayState>& displays, uint32_t flags,
+                               const InputWindowCommands& inputWindowCommands,
+                               const int64_t desiredPresentTime, bool isAutoTimestamp,
+                               const std::vector<uint64_t>& uncacheBufferIds,
+                               const int64_t postTime, bool hasListenerCallbacks,
+                               const std::vector<ListenerCallbacks>& listenerCallbacks,
+                               int originPid, int originUid, uint64_t transactionId)
+            REQUIRES(mStateLock);
     // Flush pending transactions that were presented after desiredPresentTime.
     // For test only
     bool flushTransactionQueues(VsyncId) REQUIRES(kMainThreadContext);
@@ -770,12 +767,11 @@
 
     uint32_t setClientStateLocked(const FrameTimelineInfo&, ResolvedComposerState&,
                                   int64_t desiredPresentTime, bool isAutoTimestamp,
-                                  int64_t postTime, uint32_t permissions, uint64_t transactionId)
-            REQUIRES(mStateLock);
+                                  int64_t postTime, uint64_t transactionId) REQUIRES(mStateLock);
     uint32_t updateLayerCallbacksAndStats(const FrameTimelineInfo&, ResolvedComposerState&,
                                           int64_t desiredPresentTime, bool isAutoTimestamp,
-                                          int64_t postTime, uint32_t permissions,
-                                          uint64_t transactionId) REQUIRES(mStateLock);
+                                          int64_t postTime, uint64_t transactionId)
+            REQUIRES(mStateLock);
     uint32_t getTransactionFlags() const;
 
     // Sets the masked bits, and schedules a commit if needed.
@@ -815,10 +811,10 @@
     status_t mirrorDisplay(DisplayId displayId, const LayerCreationArgs& args,
                            gui::CreateSurfaceResult& outResult);
 
-    void markLayerPendingRemovalLocked(const sp<Layer>& layer);
+    void markLayerPendingRemovalLocked(const sp<Layer>& layer) REQUIRES(mStateLock);
 
     // 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);
 
@@ -844,7 +840,9 @@
 
     // If the uid provided is not UNSET_UID, the traverse will skip any layers that don't have a
     // matching ownerUid
-    void traverseLayersInLayerStack(ui::LayerStack, const int32_t uid, const LayerVector::Visitor&);
+    void traverseLayersInLayerStack(ui::LayerStack, const int32_t uid,
+                                    std::unordered_set<uint32_t> excludeLayerIds,
+                                    const LayerVector::Visitor&);
 
     void readPersistentProperties();
 
@@ -855,8 +853,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) {
@@ -864,8 +861,9 @@
     }
 
     sp<DisplayDevice> getDisplayDeviceLocked(const wp<IBinder>& displayToken) REQUIRES(mStateLock) {
-        const sp<DisplayDevice> nullDisplay;
-        return mDisplays.get(displayToken).value_or(std::cref(nullDisplay));
+        return mDisplays.get(displayToken)
+                .or_else(ftl::static_ref<sp<DisplayDevice>>([] { return nullptr; }))
+                .value();
     }
 
     sp<const DisplayDevice> getDisplayDeviceLocked(PhysicalDisplayId id) const
@@ -1006,7 +1004,9 @@
 
     using FenceTimePtr = std::shared_ptr<FenceTime>;
 
-    const FenceTimePtr& getPreviousPresentFence(TimePoint frameTime, Period)
+    bool wouldPresentEarly(TimePoint frameTime, Period) const REQUIRES(kMainThreadContext);
+
+    const FenceTimePtr& getPreviousPresentFence(TimePoint frameTime, Period) const
             REQUIRES(kMainThreadContext);
 
     // Blocks the thread waiting for up to graceTimeMs in case the fence is about to signal.
@@ -1021,10 +1021,10 @@
      */
     sp<display::DisplayToken> getPhysicalDisplayTokenLocked(PhysicalDisplayId displayId) const
             REQUIRES(mStateLock) {
-        const sp<display::DisplayToken> nullToken;
         return mPhysicalDisplays.get(displayId)
                 .transform([](const display::PhysicalDisplay& display) { return display.token(); })
-                .value_or(std::cref(nullToken));
+                .or_else([] { return std::optional<sp<display::DisplayToken>>(nullptr); })
+                .value();
     }
 
     std::optional<PhysicalDisplayId> getPhysicalDisplayIdLocked(
@@ -1085,7 +1085,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;
@@ -1121,9 +1123,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
@@ -1202,8 +1201,14 @@
     // Tracks layers that have pending frames which are candidates for being
     // latched.
     std::unordered_set<sp<Layer>, SpHash<Layer>> mLayersWithQueuedFrames;
+    std::unordered_set<sp<Layer>, SpHash<Layer>> mLayersWithBuffersRemoved;
     // Tracks layers that need to update a display's dirty region.
     std::vector<sp<Layer>> mLayersPendingRefresh;
+    // Sorted list of layers that were composed during previous frame. This is used to
+    // avoid an expensive traversal of the layer hierarchy when there are no
+    // visible region changes. Because this is a list of strong pointers, this will
+    // extend the life of the layer but this list is only updated in the main thread.
+    std::vector<sp<Layer>> mPreviouslyComposedLayers;
 
     BootStage mBootStage = BootStage::BOOTLOADER;
 
@@ -1242,7 +1247,7 @@
     bool mLayerCachingEnabled = false;
     bool mBackpressureGpuComposition = false;
 
-    LayerTracing mLayerTracing{*this};
+    LayerTracing mLayerTracing;
     bool mLayerTracingEnabled = false;
 
     std::optional<TransactionTracing> mTransactionTracing;
@@ -1254,6 +1259,9 @@
 
     VsyncId mLastCommittedVsyncId;
 
+    VsyncId mLastInputFlingerUpdateVsyncId;
+    nsecs_t mLastInputFlingerUpdateTimestamp;
+
     // If blurs should be enabled on this device.
     bool mSupportsBlur = false;
     std::atomic<uint32_t> mFrameMissedCount = 0;
@@ -1392,6 +1400,10 @@
 
     std::atomic<ui::Transform::RotationFlags> mActiveDisplayTransformHint;
 
+    // Must only be accessed on the main thread.
+    // TODO (b/259407931): Remove.
+    static ui::Transform::RotationFlags sActiveDisplayRotationFlags;
+
     bool isRefreshRateOverlayEnabled() const REQUIRES(mStateLock) {
         return hasDisplay(
                 [](const auto& display) { return display.isRefreshRateOverlayEnabled(); });
@@ -1401,8 +1413,11 @@
             std::function<bool(const frontend::LayerSnapshot&, bool& outStopTraversal)>
                     snapshotFilterFn);
     std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()> getLayerSnapshotsForScreenshots(
+            std::optional<ui::LayerStack> layerStack, uint32_t uid,
+            std::unordered_set<uint32_t> excludeLayerIds);
+    std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()> getLayerSnapshotsForScreenshots(
             uint32_t rootLayerId, uint32_t uid, std::unordered_set<uint32_t> excludeLayerIds,
-            bool childrenOnly, const FloatRect& parentCrop);
+            bool childrenOnly, const std::optional<FloatRect>& optionalParentCrop);
 
     const sp<WindowInfosListenerInvoker> mWindowInfosListenerInvoker;
 
@@ -1424,17 +1439,17 @@
 
     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;
-    struct {
-        bool late = false;
-        bool early = false;
-    } mPowerHintSessionMode;
 
     TransactionHandler mTransactionHandler;
     display::DisplayMap<ui::LayerStack, frontend::DisplayInfo> mFrontEndDisplayInfos;
     bool mFrontEndDisplayInfosChanged = false;
+
+    // WindowInfo ids visible during the last commit.
+    std::unordered_set<int32_t> mVisibleWindowIds;
 };
 
 class SurfaceComposerAIDL : public gui::BnSurfaceComposer {
diff --git a/services/surfaceflinger/TEST_MAPPING b/services/surfaceflinger/TEST_MAPPING
index cab33ae..155a275 100644
--- a/services/surfaceflinger/TEST_MAPPING
+++ b/services/surfaceflinger/TEST_MAPPING
@@ -5,6 +5,22 @@
     },
     {
       "name": "libcompositionengine_test"
+    },
+    {
+      "name": "libgui_test",
+      "options": [
+        {
+          "native-test-flag": "--gtest_filter=\"InputSurfacesTest*:MultiDisplayTests*\""
+        }
+      ]
+    },
+    {
+      "name": "libscheduler_test"
+    }
+  ],
+  "hwasan-presubmit": [
+    {
+      "name": "libscheduler_test"
     }
   ]
 }
diff --git a/services/surfaceflinger/TracedOrdinal.h b/services/surfaceflinger/TracedOrdinal.h
index 558b3be..1adc3a5 100644
--- a/services/surfaceflinger/TracedOrdinal.h
+++ b/services/surfaceflinger/TracedOrdinal.h
@@ -24,16 +24,24 @@
 #include <cutils/compiler.h>
 #include <utils/Trace.h>
 
-namespace std {
-template <class Rep, class Period>
-bool signbit(std::chrono::duration<Rep, Period> v) {
-    return signbit(std::chrono::duration_cast<std::chrono::nanoseconds>(v).count());
-}
-} // namespace std
-
 namespace android {
 
 namespace {
+template <class Rep, class Period>
+bool signbit(std::chrono::duration<Rep, Period> v) {
+    return std::signbit(std::chrono::duration_cast<std::chrono::nanoseconds>(v).count());
+}
+
+template <typename Enum, typename std::enable_if<std::is_enum<Enum>::value>::type* = nullptr>
+bool signbit(Enum e) {
+    return std::signbit(static_cast<typename std::underlying_type<Enum>::type>(e));
+}
+
+template <typename T, typename std::enable_if<!std::is_enum<T>::value>::type* = nullptr>
+bool signbit(T t) {
+    return std::signbit(t);
+}
+
 template <typename T>
 int64_t to_int64(T v) {
     return int64_t(v);
@@ -49,14 +57,12 @@
 class TracedOrdinal {
 public:
     static_assert(std::is_same<bool, T>() || (std::is_signed<T>() && std::is_integral<T>()) ||
-                          std::is_same<std::chrono::nanoseconds, T>(),
+                          std::is_same<std::chrono::nanoseconds, T>() || std::is_enum<T>(),
                   "Type is not supported. Please test it with systrace before adding "
                   "it to the list.");
 
     TracedOrdinal(std::string name, T initialValue)
-          : mName(std::move(name)),
-            mHasGoneNegative(std::signbit(initialValue)),
-            mData(initialValue) {
+          : mName(std::move(name)), mHasGoneNegative(signbit(initialValue)), mData(initialValue) {
         trace();
     }
 
@@ -66,7 +72,7 @@
 
     TracedOrdinal& operator=(T other) {
         mData = other;
-        mHasGoneNegative = mHasGoneNegative || std::signbit(mData);
+        mHasGoneNegative = mHasGoneNegative || signbit(mData);
         trace();
         return *this;
     }
@@ -81,7 +87,7 @@
             mNameNegative = mName + "Negative";
         }
 
-        if (!std::signbit(mData)) {
+        if (!signbit(mData)) {
             ATRACE_INT64(mName.c_str(), to_int64(mData));
             if (mHasGoneNegative) {
                 ATRACE_INT64(mNameNegative.c_str(), 0);
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..0694180 100644
--- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
+++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
@@ -15,15 +15,42 @@
  */
 
 #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 = 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 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; }
+    void remapBuffer() override {}
+    ~FakeExternalTexture() = default;
+};
+
 proto::TransactionState TransactionProtoParser::toProto(const TransactionState& t) {
     proto::TransactionState proto;
     proto.set_pid(t.originPid);
@@ -35,51 +62,40 @@
 
     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()));
     for (auto& displayState : t.displays) {
         proto.mutable_display_changes()->Add(std::move(toProto(displayState)));
     }
+
+    proto.mutable_merged_transaction_ids()->Reserve(
+            static_cast<int32_t>(t.mergedTransactionIds.size()));
+    for (auto& mergedTransactionId : t.mergedTransactionIds) {
+        proto.mutable_merged_transaction_ids()->Add(mergedTransactionId);
+    }
+
     return proto;
 }
 
 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 +151,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 +181,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 +203,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 +212,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 +284,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 +310,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 +323,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 +418,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 +450,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 +473,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 +551,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.isVirtual = 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..d6c98e1 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,31 @@
     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&);
+    static 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;
+    static frontend::DisplayInfo fromProto(const proto::DisplayInfo&);
+    static 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..87a633f 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,29 @@
     // 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());
+                // TODO(b/238781169) make this log fatal when we switch over to using new fe
+                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 +314,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..55004c5 100644
--- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
@@ -18,171 +18,27 @@
 #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 <ios>
 #include <string>
+#include <vector>
+#include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/RequestedLayerState.h"
+#include "LayerProtoHelper.h"
+#include "Tracing/LayerTracing.h"
+#include "TransactionState.h"
+#include "cutils/properties.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 +47,124 @@
         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);
+            ALOGV("       %s", args.getDebugString().c_str());
+            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);
+            for (auto& resolvedComposerState : transaction.states) {
+                if (resolvedComposerState.state.what & layer_state_t::eInputInfoChanged) {
+                    if (!resolvedComposerState.state.windowInfoHandle->getInfo()->inputConfig.test(
+                                gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL)) {
+                        // create a fake token since the FE expects a valid token
+                        resolvedComposerState.state.windowInfoHandle->editInfo()->token =
+                                sp<BBinder>::make();
+                    }
+                }
+            }
+            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));
+        for (int j = 0; j < entry.destroyed_layers_size(); j++) {
+            ALOGV("       destroyedHandles=%d", entry.destroyed_layers(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++) {
+            ALOGV("       destroyedHandles=%d", entry.destroyed_layer_handles(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, /*ignoreUnknownHandles=*/true);
+        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();
+
+        LayersProto layersProto = LayerProtoFromSnapshotGenerator(snapshotBuilder, displayInfos, {},
+                                                                  layerTracing.getFlags())
+                                          .generate(hierarchyBuilder.getHierarchy());
+        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..7132a59 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 {
@@ -49,9 +54,10 @@
                      const Vector<DisplayState>& displayStates, uint32_t transactionFlags,
                      const sp<IBinder>& applyToken, const InputWindowCommands& inputWindowCommands,
                      int64_t desiredPresentTime, bool isAutoTimestamp,
-                     std::vector<uint64_t> uncacheBufferIds, int64_t postTime, uint32_t permissions,
+                     std::vector<uint64_t> uncacheBufferIds, int64_t postTime,
                      bool hasListenerCallbacks, std::vector<ListenerCallbacks> listenerCallbacks,
-                     int originPid, int originUid, uint64_t transactionId)
+                     int originPid, int originUid, uint64_t transactionId,
+                     std::vector<uint64_t> mergedTransactionIds)
           : frameTimelineInfo(frameTimelineInfo),
             states(std::move(composerStates)),
             displays(displayStates),
@@ -62,19 +68,18 @@
             isAutoTimestamp(isAutoTimestamp),
             uncacheBufferIds(std::move(uncacheBufferIds)),
             postTime(postTime),
-            permissions(permissions),
             hasListenerCallbacks(hasListenerCallbacks),
             listenerCallbacks(listenerCallbacks),
             originPid(originPid),
             originUid(originUid),
-            id(transactionId) {}
+            id(transactionId),
+            mergedTransactionIds(std::move(mergedTransactionIds)) {}
 
     // Invokes `void(const layer_state_t&)` visitor for matching layers.
     template <typename Visitor>
     void traverseStatesWithBuffers(Visitor&& visitor) const {
         for (const auto& state : states) {
-            if (state.state.hasBufferChanges() && state.state.hasValidBuffer() &&
-                state.state.surface) {
+            if (state.state.hasBufferChanges() && state.externalTexture && state.state.surface) {
                 visitor(state.state);
             }
         }
@@ -83,9 +88,8 @@
     template <typename Visitor>
     void traverseStatesWithBuffersWhileTrue(Visitor&& visitor) {
         for (auto state = states.begin(); state != states.end();) {
-            if (state->state.hasBufferChanges() && state->state.hasValidBuffer() &&
-                state->state.surface) {
-                int result = visitor(state->state);
+            if (state->state.hasBufferChanges() && state->externalTexture && state->state.surface) {
+                int result = visitor(state->state, state->externalTexture);
                 if (result == STOP_TRAVERSAL) return;
                 if (result == DELETE_AND_CONTINUE_TRAVERSAL) {
                     state = states.erase(state);
@@ -123,13 +127,13 @@
     bool isAutoTimestamp;
     std::vector<uint64_t> uncacheBufferIds;
     int64_t postTime;
-    uint32_t permissions;
     bool hasListenerCallbacks;
     std::vector<ListenerCallbacks> listenerCallbacks;
     int originPid;
     int originUid;
     uint64_t id;
     bool sentFenceTimeoutWarning = false;
+    std::vector<uint64_t> mergedTransactionIds;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
index 292083b..2b62638 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
@@ -16,6 +16,7 @@
 
 #include <ftl/small_vector.h>
 #include <gui/ISurfaceComposer.h>
+#include <gui/WindowInfosUpdate.h>
 
 #include "WindowInfosListenerInvoker.h"
 
@@ -25,20 +26,17 @@
 using gui::IWindowInfosListener;
 using gui::WindowInfo;
 
-struct WindowInfosListenerInvoker::WindowInfosReportedListener : gui::BnWindowInfosReportedListener,
-                                                                 DeathRecipient {
-    explicit WindowInfosReportedListener(
-            size_t callbackCount,
-            const std::unordered_set<sp<gui::IWindowInfosReportedListener>,
-                                     SpHash<gui::IWindowInfosReportedListener>>&
-                    windowInfosReportedListeners)
-          : mCallbacksPending(callbackCount),
-            mWindowInfosReportedListeners(windowInfosReportedListeners) {}
+using WindowInfosListenerVector = ftl::SmallVector<const sp<IWindowInfosListener>, 3>;
+
+struct WindowInfosReportedListenerInvoker : gui::BnWindowInfosReportedListener,
+                                            IBinder::DeathRecipient {
+    WindowInfosReportedListenerInvoker(WindowInfosListenerVector windowInfosListeners,
+                                       WindowInfosReportedListenerSet windowInfosReportedListeners)
+          : mCallbacksPending(windowInfosListeners.size()),
+            mWindowInfosListeners(std::move(windowInfosListeners)),
+            mWindowInfosReportedListeners(std::move(windowInfosReportedListeners)) {}
 
     binder::Status onWindowInfosReported() override {
-        // TODO(b/222421815) There could potentially be callbacks that we don't need to wait for
-        // before calling the WindowInfosReportedListeners coming from InputWindowCommands. Filter
-        // the list of callbacks down to those from system server.
         if (--mCallbacksPending == 0) {
             for (const auto& listener : mWindowInfosReportedListeners) {
                 sp<IBinder> asBinder = IInterface::asBinder(listener);
@@ -46,6 +44,12 @@
                     listener->onWindowInfosReported();
                 }
             }
+
+            auto wpThis = wp<WindowInfosReportedListenerInvoker>::fromExisting(this);
+            for (const auto& listener : mWindowInfosListeners) {
+                sp<IBinder> binder = IInterface::asBinder(listener);
+                binder->unlinkToDeath(wpThis);
+            }
         }
         return binder::Status::ok();
     }
@@ -54,9 +58,9 @@
 
 private:
     std::atomic<size_t> mCallbacksPending;
-    std::unordered_set<sp<gui::IWindowInfosReportedListener>,
-                       SpHash<gui::IWindowInfosReportedListener>>
-            mWindowInfosReportedListeners;
+    static constexpr size_t kStaticCapacity = 3;
+    const WindowInfosListenerVector mWindowInfosListeners;
+    WindowInfosReportedListenerSet mWindowInfosReportedListeners;
 };
 
 void WindowInfosListenerInvoker::addWindowInfosListener(sp<IWindowInfosListener> listener) {
@@ -82,38 +86,90 @@
 }
 
 void WindowInfosListenerInvoker::windowInfosChanged(
-        const std::vector<WindowInfo>& windowInfos, const std::vector<DisplayInfo>& displayInfos,
-        const std::unordered_set<sp<gui::IWindowInfosReportedListener>,
-                                 SpHash<gui::IWindowInfosReportedListener>>&
-                windowInfosReportedListeners) {
-    ftl::SmallVector<const sp<IWindowInfosListener>, kStaticCapacity> windowInfosListeners;
+        std::vector<WindowInfo> windowInfos, std::vector<DisplayInfo> displayInfos,
+        WindowInfosReportedListenerSet reportedListeners, bool forceImmediateCall, VsyncId vsyncId,
+        nsecs_t timestamp) {
+    reportedListeners.insert(sp<WindowInfosListenerInvoker>::fromExisting(this));
+    auto callListeners = [this, windowInfos = std::move(windowInfos),
+                          displayInfos = std::move(displayInfos), vsyncId,
+                          timestamp](WindowInfosReportedListenerSet reportedListeners) mutable {
+        WindowInfosListenerVector windowInfosListeners;
+        {
+            std::scoped_lock lock(mListenersMutex);
+            for (const auto& [_, listener] : mWindowInfosListeners) {
+                windowInfosListeners.push_back(listener);
+            }
+        }
+
+        auto reportedInvoker =
+                sp<WindowInfosReportedListenerInvoker>::make(windowInfosListeners,
+                                                             std::move(reportedListeners));
+
+        gui::WindowInfosUpdate update(std::move(windowInfos), std::move(displayInfos),
+                                      vsyncId.value, timestamp);
+
+        for (const auto& listener : windowInfosListeners) {
+            sp<IBinder> asBinder = IInterface::asBinder(listener);
+
+            // linkToDeath is used here to ensure that the windowInfosReportedListeners
+            // are called even if one of the windowInfosListeners dies before
+            // calling onWindowInfosReported.
+            asBinder->linkToDeath(reportedInvoker);
+
+            auto status = listener->onWindowInfosChanged(update, reportedInvoker);
+            if (!status.isOk()) {
+                reportedInvoker->onWindowInfosReported();
+            }
+        }
+    };
+
     {
-        std::scoped_lock lock(mListenersMutex);
-        for (const auto& [_, listener] : mWindowInfosListeners) {
-            windowInfosListeners.push_back(listener);
+        std::scoped_lock lock(mMessagesMutex);
+        // If there are unacked messages and this isn't a forced call, then return immediately.
+        // If a forced window infos change doesn't happen first, the update will be sent after
+        // the WindowInfosReportedListeners are called. If a forced window infos change happens or
+        // if there are subsequent delayed messages before this update is sent, then this message
+        // will be dropped and the listeners will only be called with the latest info. This is done
+        // to reduce the amount of binder memory used.
+        if (mActiveMessageCount > 0 && !forceImmediateCall) {
+            mWindowInfosChangedDelayed = std::move(callListeners);
+            mUnsentVsyncId = vsyncId;
+            mUnsentTimestamp = timestamp;
+            mReportedListenersDelayed.merge(reportedListeners);
+            return;
         }
+
+        mWindowInfosChangedDelayed = nullptr;
+        mUnsentVsyncId = {-1};
+        mUnsentTimestamp = -1;
+        reportedListeners.merge(mReportedListenersDelayed);
+        mActiveMessageCount++;
+    }
+    callListeners(std::move(reportedListeners));
+}
+
+binder::Status WindowInfosListenerInvoker::onWindowInfosReported() {
+    std::function<void(WindowInfosReportedListenerSet)> callListeners;
+    WindowInfosReportedListenerSet reportedListeners;
+
+    {
+        std::scoped_lock lock{mMessagesMutex};
+        mActiveMessageCount--;
+        if (!mWindowInfosChangedDelayed || mActiveMessageCount > 0) {
+            return binder::Status::ok();
+        }
+
+        mActiveMessageCount++;
+        callListeners = std::move(mWindowInfosChangedDelayed);
+        mWindowInfosChangedDelayed = nullptr;
+        mUnsentVsyncId = {-1};
+        mUnsentTimestamp = -1;
+        reportedListeners = std::move(mReportedListenersDelayed);
+        mReportedListenersDelayed.clear();
     }
 
-    auto windowInfosReportedListener = windowInfosReportedListeners.empty()
-            ? nullptr
-            : sp<WindowInfosReportedListener>::make(windowInfosListeners.size(),
-                                                    windowInfosReportedListeners);
-    for (const auto& listener : windowInfosListeners) {
-        sp<IBinder> asBinder = IInterface::asBinder(listener);
-
-        // linkToDeath is used here to ensure that the windowInfosReportedListeners
-        // are called even if one of the windowInfosListeners dies before
-        // calling onWindowInfosReported.
-        if (windowInfosReportedListener) {
-            asBinder->linkToDeath(windowInfosReportedListener);
-        }
-
-        auto status = listener->onWindowInfosChanged(windowInfos, displayInfos,
-                                                     windowInfosReportedListener);
-        if (windowInfosReportedListener && !status.isOk()) {
-            windowInfosReportedListener->onWindowInfosReported();
-        }
-    }
+    callListeners(std::move(reportedListeners));
+    return binder::Status::ok();
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.h b/services/surfaceflinger/WindowInfosListenerInvoker.h
index d60a9c4..e35d056 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.h
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.h
@@ -23,34 +23,61 @@
 #include <android/gui/IWindowInfosReportedListener.h>
 #include <binder/IBinder.h>
 #include <ftl/small_map.h>
+#include <gui/SpHash.h>
 #include <utils/Mutex.h>
 
+#include "scheduler/VsyncId.h"
+
 namespace android {
 
-class SurfaceFlinger;
+using WindowInfosReportedListenerSet =
+        std::unordered_set<sp<gui::IWindowInfosReportedListener>,
+                           gui::SpHash<gui::IWindowInfosReportedListener>>;
 
-class WindowInfosListenerInvoker : public IBinder::DeathRecipient {
+class WindowInfosListenerInvoker : public gui::BnWindowInfosReportedListener,
+                                   public IBinder::DeathRecipient {
 public:
     void addWindowInfosListener(sp<gui::IWindowInfosListener>);
     void removeWindowInfosListener(const sp<gui::IWindowInfosListener>& windowInfosListener);
 
-    void windowInfosChanged(const std::vector<gui::WindowInfo>&,
-                            const std::vector<gui::DisplayInfo>&,
-                            const std::unordered_set<sp<gui::IWindowInfosReportedListener>,
-                                                     SpHash<gui::IWindowInfosReportedListener>>&
-                                    windowInfosReportedListeners);
+    void windowInfosChanged(std::vector<gui::WindowInfo>, std::vector<gui::DisplayInfo>,
+                            WindowInfosReportedListenerSet windowInfosReportedListeners,
+                            bool forceImmediateCall, VsyncId vsyncId, nsecs_t timestamp);
+
+    binder::Status onWindowInfosReported() override;
+
+    VsyncId getUnsentMessageVsyncId() {
+        std::scoped_lock lock(mMessagesMutex);
+        return mUnsentVsyncId;
+    }
+
+    nsecs_t getUnsentMessageTimestamp() {
+        std::scoped_lock lock(mMessagesMutex);
+        return mUnsentTimestamp;
+    }
+
+    uint32_t getPendingMessageCount() {
+        std::scoped_lock lock(mMessagesMutex);
+        return mActiveMessageCount;
+    }
 
 protected:
     void binderDied(const wp<IBinder>& who) override;
 
 private:
-    struct WindowInfosReportedListener;
-
     std::mutex mListenersMutex;
 
     static constexpr size_t kStaticCapacity = 3;
     ftl::SmallMap<wp<IBinder>, const sp<gui::IWindowInfosListener>, kStaticCapacity>
             mWindowInfosListeners GUARDED_BY(mListenersMutex);
+
+    std::mutex mMessagesMutex;
+    uint32_t mActiveMessageCount GUARDED_BY(mMessagesMutex) = 0;
+    std::function<void(WindowInfosReportedListenerSet)> mWindowInfosChangedDelayed
+            GUARDED_BY(mMessagesMutex);
+    VsyncId mUnsentVsyncId GUARDED_BY(mMessagesMutex) = {-1};
+    nsecs_t mUnsentTimestamp GUARDED_BY(mMessagesMutex) = -1;
+    WindowInfosReportedListenerSet mReportedListenersDelayed;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/fuzzer/Android.bp b/services/surfaceflinger/fuzzer/Android.bp
index 7350e09..f76a8d7 100644
--- a/services/surfaceflinger/fuzzer/Android.bp
+++ b/services/surfaceflinger/fuzzer/Android.bp
@@ -69,6 +69,7 @@
         "-Wno-unused-result",
         "-Wno-conversion",
         "-Wno-sign-compare",
+        "-Wno-unused-function",
     ],
     fuzz_config: {
         cc: [
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
index 8a6af10..9fac14e 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
@@ -222,7 +222,7 @@
     std::optional<impl::HWComposer::DeviceRequestedChanges> outChanges;
     mHwc.getDeviceCompositionChanges(halDisplayID,
                                      mFdp.ConsumeBool() /*frameUsesClientComposition*/,
-                                     std::chrono::steady_clock::now(), FenceTime::NO_FENCE,
+                                     std::chrono::steady_clock::now(),
                                      mFdp.ConsumeIntegral<nsecs_t>(), &outChanges);
 }
 
@@ -327,7 +327,6 @@
     invokeComposerHal2_4(&composer, display, outLayer);
 
     composer.executeCommands(display);
-    composer.resetCommands(display);
 
     composer.destroyLayer(display, outLayer);
     composer.destroyVirtualDisplay(display);
@@ -555,8 +554,7 @@
     mHwc.setClientTarget(halDisplayID, mFdp.ConsumeIntegral<uint32_t>(), Fence::NO_FENCE,
                          sp<GraphicBuffer>::make(), mFdp.PickValueInArray(kDataspaces));
 
-    mHwc.presentAndGetReleaseFences(halDisplayID, std::chrono::steady_clock::now(),
-                                    FenceTime::NO_FENCE);
+    mHwc.presentAndGetReleaseFences(halDisplayID, std::chrono::steady_clock::now());
 
     mHwc.setPowerMode(mPhysicalDisplayId, mFdp.PickValueInArray(kPowerModes));
 
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
index ce4d18f..80943b5 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
@@ -185,11 +185,12 @@
     bool hasListenerCallbacks = mFdp.ConsumeBool();
     std::vector<ListenerCallbacks> listenerCallbacks{};
     uint64_t transactionId = mFdp.ConsumeIntegral<uint64_t>();
+    std::vector<uint64_t> mergedTransactionIds{};
 
     mTestableFlinger.setTransactionState(FrameTimelineInfo{}, states, displays, flags, applyToken,
                                          InputWindowCommands{}, desiredPresentTime, isAutoTimestamp,
-                                         {}, hasListenerCallbacks, listenerCallbacks,
-                                         transactionId);
+                                         {}, hasListenerCallbacks, listenerCallbacks, transactionId,
+                                         mergedTransactionIds);
 }
 
 void SurfaceFlingerFuzzer::setDisplayStateLocked() {
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index f27f53b..da5ec48 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -49,6 +49,7 @@
 #include "SurfaceFlingerDefaultFactory.h"
 #include "ThreadContext.h"
 #include "TimeStats/TimeStats.h"
+#include "surfaceflinger_scheduler_fuzzer.h"
 
 #include "renderengine/mock/RenderEngine.h"
 #include "scheduler/TimeKeeper.h"
@@ -237,7 +238,8 @@
         const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
         registerDisplayInternal(displayId, std::move(selectorPtr),
                                 std::shared_ptr<VsyncSchedule>(
-                                        new VsyncSchedule(displayId, std::move(tracker), nullptr,
+                                        new VsyncSchedule(displayId, std::move(tracker),
+                                                          std::make_shared<FuzzImplVSyncDispatch>(),
                                                           std::move(controller))));
     }
 
@@ -402,9 +404,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 +454,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 +542,7 @@
                 mFlinger->createDisplay(String8(fdp->ConsumeRandomLengthString().c_str()),
                                         fdp->ConsumeBool());
 
-        onInitializeDisplays();
+        initializeDisplays();
         mFlinger->getPhysicalDisplayToken(physicalDisplayId);
 
         mFlinger->mStartPropertySetThread =
@@ -590,7 +590,7 @@
         mFlinger->binderDied(display);
         mFlinger->onFirstRef();
 
-        mFlinger->updateInputFlinger();
+        mFlinger->updateInputFlinger(VsyncId{0});
         mFlinger->updateCursorAsync();
 
         mutableScheduler().setVsyncConfig({.sfOffset = mFdp.ConsumeIntegral<nsecs_t>(),
@@ -737,19 +737,18 @@
         return mFlinger->mTransactionHandler.mPendingTransactionQueues;
     }
 
-    auto setTransactionState(const FrameTimelineInfo& frameTimelineInfo,
-                             Vector<ComposerState>& states, const Vector<DisplayState>& displays,
-                             uint32_t flags, const sp<IBinder>& applyToken,
-                             const InputWindowCommands& inputWindowCommands,
-                             int64_t desiredPresentTime, bool isAutoTimestamp,
-                             const std::vector<client_cache_t>& uncacheBuffers,
-                             bool hasListenerCallbacks,
-                             std::vector<ListenerCallbacks>& listenerCallbacks,
-                             uint64_t transactionId) {
+    auto setTransactionState(
+            const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& states,
+            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
+            bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffers,
+            bool hasListenerCallbacks, std::vector<ListenerCallbacks>& listenerCallbacks,
+            uint64_t transactionId, const std::vector<uint64_t>& mergedTransactionIds) {
         return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken,
                                              inputWindowCommands, desiredPresentTime,
                                              isAutoTimestamp, uncacheBuffers, hasListenerCallbacks,
-                                             listenerCallbacks, transactionId);
+                                             listenerCallbacks, transactionId,
+                                             mergedTransactionIds);
     }
 
     auto flushTransactionQueues() {
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
index c088e7b..921cae4 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
@@ -25,6 +25,7 @@
 #include <gui/WindowInfo.h>
 #include <renderengine/mock/FakeExternalTexture.h>
 #include <ui/DisplayStatInfo.h>
+#include <ui/Transform.h>
 
 #include <FuzzableDataspaces.h>
 #include <surfaceflinger_fuzzers_utils.h>
@@ -42,6 +43,7 @@
     void invokeEffectLayer();
     LayerCreationArgs createLayerCreationArgs(TestableSurfaceFlinger* flinger, sp<Client> client);
     Rect getFuzzedRect();
+    ui::Transform getFuzzedTransform();
     FrameTimelineInfo getFuzzedFrameTimelineInfo();
 
 private:
@@ -54,6 +56,12 @@
                 mFdp.ConsumeIntegral<int32_t>() /*bottom*/);
 }
 
+ui::Transform LayerFuzzer::getFuzzedTransform() {
+    return ui::Transform(mFdp.ConsumeIntegral<int32_t>() /*orientation*/,
+                         mFdp.ConsumeIntegral<int32_t>() /*width*/,
+                         mFdp.ConsumeIntegral<int32_t>() /*height*/);
+}
+
 FrameTimelineInfo LayerFuzzer::getFuzzedFrameTimelineInfo() {
     FrameTimelineInfo ftInfo;
     ftInfo.vsyncId = mFdp.ConsumeIntegral<int64_t>();
@@ -117,9 +125,12 @@
                                             mFdp.ConsumeIntegral<int64_t>(),
                                             mFdp.ConsumeIntegral<int64_t>());
 
-    layer->onLayerDisplayed(ftl::yield<FenceResult>(fence).share());
-    layer->onLayerDisplayed(
-            ftl::yield<FenceResult>(base::unexpected(mFdp.ConsumeIntegral<status_t>())).share());
+    layer->onLayerDisplayed(ftl::yield<FenceResult>(fence).share(),
+                            ui::LayerStack::fromValue(mFdp.ConsumeIntegral<uint32_t>()));
+    layer->onLayerDisplayed(ftl::yield<FenceResult>(
+                                    base::unexpected(mFdp.ConsumeIntegral<status_t>()))
+                                    .share(),
+                            ui::LayerStack::fromValue(mFdp.ConsumeIntegral<uint32_t>()));
 
     layer->releasePendingBuffer(mFdp.ConsumeIntegral<int64_t>());
     layer->onPostComposition(nullptr, fenceTime, fenceTime, compositorTiming);
@@ -166,6 +177,7 @@
                               {mFdp.ConsumeIntegral<int32_t>(),
                                mFdp.ConsumeIntegral<int32_t>()} /*reqSize*/,
                               mFdp.PickValueInArray(kDataspaces), mFdp.ConsumeBool(),
+                              mFdp.ConsumeBool(), getFuzzedTransform(), getFuzzedRect(),
                               mFdp.ConsumeBool());
     layerArea.render([]() {} /*drawLayers*/);
 
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
index e6be9a8..8061a8f 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
@@ -75,7 +75,7 @@
 
     bool isVisible() const override { return true; }
 
-    sp<Layer> createClone() override { return nullptr; }
+    sp<Layer> createClone(uint32_t /* mirrorRootId */) override { return nullptr; }
 };
 
 class FuzzImplVSyncTracker : public scheduler::VSyncTracker {
@@ -129,6 +129,11 @@
         return (scheduler::ScheduleResult)0;
     }
 
+    scheduler::ScheduleResult update(CallbackToken /* token */,
+                                     ScheduleTiming /* scheduleTiming */) override {
+        return (scheduler::ScheduleResult)0;
+    }
+
     scheduler::CancelResult cancel(CallbackToken /* token */) override {
         return (scheduler::CancelResult)0;
     }
diff --git a/services/surfaceflinger/layerproto/layers.proto b/services/surfaceflinger/layerproto/layers.proto
index 3598308..e9add2e 100644
--- a/services/surfaceflinger/layerproto/layers.proto
+++ b/services/surfaceflinger/layerproto/layers.proto
@@ -138,6 +138,8 @@
   float requested_corner_radius = 56;
 
   RectProto destination_frame = 57;
+
+  uint32 original_id = 58;
 }
 
 message PositionProto {
diff --git a/services/surfaceflinger/layerproto/transactions.proto b/services/surfaceflinger/layerproto/transactions.proto
index 4c6a9cf..b0cee9b 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 {
@@ -70,11 +100,12 @@
     uint64 transaction_id = 6;
     repeated LayerState layer_changes = 7;
     repeated DisplayState display_changes = 8;
+    repeated uint64 merged_transaction_ids = 9;
 }
 
 // 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 +195,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 +251,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 +259,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/main_surfaceflinger.cpp b/services/surfaceflinger/main_surfaceflinger.cpp
index 0495678..cf23169 100644
--- a/services/surfaceflinger/main_surfaceflinger.cpp
+++ b/services/surfaceflinger/main_surfaceflinger.cpp
@@ -91,7 +91,7 @@
     // Set uclamp.min setting on all threads, maybe an overkill but we want
     // to cover important threads like RenderEngine.
     if (SurfaceFlinger::setSchedAttr(true) != NO_ERROR) {
-        ALOGW("Couldn't set uclamp.min: %s\n", strerror(errno));
+        ALOGW("Failed to set uclamp.min during boot: %s", strerror(errno));
     }
 
     // The binder threadpool we start will inherit sched policy and priority
@@ -155,7 +155,7 @@
     startDisplayService(); // dependency on SF getting registered above
 
     if (SurfaceFlinger::setSchedFifo(true) != NO_ERROR) {
-        ALOGW("Couldn't set to SCHED_FIFO: %s", strerror(errno));
+        ALOGW("Failed to set SCHED_FIFO during boot: %s", strerror(errno));
     }
 
     // run surface flinger in this thread
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/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp
index 4a45eb5..69e9a16 100644
--- a/services/surfaceflinger/tests/Credentials_test.cpp
+++ b/services/surfaceflinger/tests/Credentials_test.cpp
@@ -31,6 +31,7 @@
 #include <utils/String8.h>
 #include <functional>
 #include "utils/ScreenshotUtils.h"
+#include "utils/WindowInfosListenerUtils.h"
 
 namespace android {
 
@@ -378,6 +379,58 @@
     ASSERT_NE(static_cast<ColorMode>(BAD_VALUE), colorMode);
 }
 
+TEST_F(CredentialsTest, TransactionPermissionTest) {
+    WindowInfosListenerUtils windowInfosListenerUtils;
+    std::string name = "Test Layer";
+    sp<IBinder> token = sp<BBinder>::make();
+    WindowInfo windowInfo;
+    windowInfo.name = name;
+    windowInfo.token = token;
+    sp<SurfaceControl> surfaceControl =
+            mComposerClient->createSurface(String8(name.c_str()), 100, 100, PIXEL_FORMAT_RGBA_8888,
+                                           ISurfaceComposerClient::eFXSurfaceBufferState);
+    const Rect crop(0, 0, 100, 100);
+    {
+        UIDFaker f(AID_SYSTEM);
+        Transaction()
+                .setLayerStack(surfaceControl, ui::DEFAULT_LAYER_STACK)
+                .show(surfaceControl)
+                .setLayer(surfaceControl, INT32_MAX - 1)
+                .setCrop(surfaceControl, crop)
+                .setInputWindowInfo(surfaceControl, windowInfo)
+                .apply();
+    }
+
+    // Called from non privileged process
+    Transaction().setTrustedOverlay(surfaceControl, true);
+    {
+        UIDFaker f(AID_SYSTEM);
+        auto windowIsPresentAndNotTrusted = [&](const std::vector<WindowInfo>& windowInfos) {
+            auto foundWindowInfo =
+                    WindowInfosListenerUtils::findMatchingWindowInfo(windowInfo, windowInfos);
+            if (!foundWindowInfo) {
+                return false;
+            }
+            return !foundWindowInfo->inputConfig.test(WindowInfo::InputConfig::TRUSTED_OVERLAY);
+        };
+        windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndNotTrusted);
+    }
+
+    {
+        UIDFaker f(AID_SYSTEM);
+        Transaction().setTrustedOverlay(surfaceControl, true);
+        auto windowIsPresentAndTrusted = [&](const std::vector<WindowInfo>& windowInfos) {
+            auto foundWindowInfo =
+                    WindowInfosListenerUtils::findMatchingWindowInfo(windowInfo, windowInfos);
+            if (!foundWindowInfo) {
+                return false;
+            }
+            return foundWindowInfo->inputConfig.test(WindowInfo::InputConfig::TRUSTED_OVERLAY);
+        };
+        windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndTrusted);
+    }
+}
+
 } // namespace android
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp b/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp
index 0df7e2f..4c26017 100644
--- a/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp
+++ b/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp
@@ -33,9 +33,14 @@
 
     const VsyncEventData& vsyncEventData = parcelableVsyncEventData.vsync;
     EXPECT_NE(std::numeric_limits<size_t>::max(), vsyncEventData.preferredFrameTimelineIndex);
+    EXPECT_GT(static_cast<int64_t>(vsyncEventData.frameTimelinesLength), 0)
+            << "Frame timelines length should be greater than 0";
+    EXPECT_LE(static_cast<int64_t>(vsyncEventData.frameTimelinesLength),
+              VsyncEventData::kFrameTimelinesCapacity)
+            << "Frame timelines length should not exceed max capacity";
     EXPECT_GT(vsyncEventData.frameTimelines[0].deadlineTimestamp, now)
             << "Deadline timestamp should be greater than frame time";
-    for (size_t i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
+    for (size_t i = 0; i < vsyncEventData.frameTimelinesLength; i++) {
         EXPECT_NE(gui::FrameTimelineInfo::INVALID_VSYNC_ID,
                   vsyncEventData.frameTimelines[i].vsyncId);
         EXPECT_GT(vsyncEventData.frameTimelines[i].expectedPresentationTime,
diff --git a/services/surfaceflinger/tests/LayerCallback_test.cpp b/services/surfaceflinger/tests/LayerCallback_test.cpp
index 26dbc76..79886bd 100644
--- a/services/surfaceflinger/tests/LayerCallback_test.cpp
+++ b/services/surfaceflinger/tests/LayerCallback_test.cpp
@@ -1224,4 +1224,75 @@
     EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true));
 }
 
+TEST_F(LayerCallbackTest, SetNullBuffer) {
+    sp<SurfaceControl> layer;
+    ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer());
+
+    Transaction transaction;
+    CallbackHelper callback;
+    int err = fillTransaction(transaction, &callback, layer, /*setBuffer=*/true,
+                              /*setBackgroundColor=*/false);
+    if (err) {
+        GTEST_SUCCEED() << "test not supported";
+        return;
+    }
+    transaction.apply();
+
+    {
+        ExpectedResult expected;
+        expected.addSurface(ExpectedResult::Transaction::PRESENTED, layer,
+                            ExpectedResult::Buffer::ACQUIRED,
+                            ExpectedResult::PreviousBuffer::NOT_RELEASED);
+        EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true));
+    }
+
+    transaction.setBuffer(layer, nullptr);
+    transaction.addTransactionCompletedCallback(callback.function, callback.getContext());
+    transaction.apply();
+
+    {
+        ExpectedResult expected;
+        expected.addSurface(ExpectedResult::Transaction::PRESENTED, layer,
+                            ExpectedResult::Buffer::ACQUIRED_NULL,
+                            ExpectedResult::PreviousBuffer::RELEASED);
+        EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true));
+    }
+
+    err = fillTransaction(transaction, &callback, layer, /*setBuffer=*/true,
+                          /*setBackgroundColor=*/false);
+    if (err) {
+        GTEST_SUCCEED() << "test not supported";
+        return;
+    }
+
+    transaction.apply();
+
+    {
+        ExpectedResult expected;
+        expected.addSurface(ExpectedResult::Transaction::PRESENTED, layer,
+                            ExpectedResult::Buffer::ACQUIRED,
+                            ExpectedResult::PreviousBuffer::NOT_RELEASED);
+        EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true));
+    }
+}
+
+TEST_F(LayerCallbackTest, SetNullBufferOnLayerWithoutBuffer) {
+    sp<SurfaceControl> layer;
+    ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer());
+
+    Transaction transaction;
+    transaction.setBuffer(layer, nullptr);
+    CallbackHelper callback;
+    transaction.addTransactionCompletedCallback(callback.function, callback.getContext());
+    transaction.apply();
+
+    {
+        ExpectedResult expected;
+        expected.addSurface(ExpectedResult::Transaction::NOT_PRESENTED, layer,
+                            ExpectedResult::Buffer::NOT_ACQUIRED,
+                            ExpectedResult::PreviousBuffer::NOT_RELEASED);
+        EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true));
+    }
+}
+
 } // namespace android
diff --git a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
index 0b8c51e..b8068f7 100644
--- a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
+++ b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
@@ -1636,6 +1636,65 @@
         getScreenCapture()->expectColor(Rect(0, 0, 32, 32), expectedColor, tolerance);
     }
 }
+
+TEST_P(LayerRenderTypeTransactionTest, SetNullBuffer) {
+    const Rect bounds(0, 0, 32, 32);
+    sp<SurfaceControl> layer;
+    ASSERT_NO_FATAL_FAILURE(
+            layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState));
+
+    sp<GraphicBuffer> buffer =
+            sp<GraphicBuffer>::make(32u, 32u, PIXEL_FORMAT_RGBA_8888, 1u, kUsageFlags, "test");
+
+    ASSERT_NO_FATAL_FAILURE(TransactionUtils::fillGraphicBufferColor(buffer, bounds, Color::GREEN));
+    Transaction().setBuffer(layer, buffer).apply();
+    {
+        SCOPED_TRACE("before null buffer");
+        auto shot = getScreenCapture();
+        shot->expectColor(bounds, Color::GREEN);
+    }
+
+    Transaction().setBuffer(layer, nullptr).apply();
+    {
+        SCOPED_TRACE("null buffer removes buffer");
+        auto shot = getScreenCapture();
+        shot->expectColor(bounds, Color::BLACK);
+    }
+
+    Transaction().setBuffer(layer, buffer).apply();
+    {
+        SCOPED_TRACE("after null buffer");
+        auto shot = getScreenCapture();
+        shot->expectColor(bounds, Color::GREEN);
+    }
+}
+
+TEST_P(LayerRenderTypeTransactionTest, SetNullBufferOnLayerWithoutBuffer) {
+    const Rect bounds(0, 0, 32, 32);
+    sp<SurfaceControl> layer;
+    ASSERT_NO_FATAL_FAILURE(
+            layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState));
+    {
+        SCOPED_TRACE("starting state");
+        auto shot = getScreenCapture();
+        shot->expectColor(bounds, Color::BLACK);
+    }
+
+    Transaction().setBuffer(layer, nullptr).apply();
+    {
+        SCOPED_TRACE("null buffer has no effect");
+        auto shot = getScreenCapture();
+        shot->expectColor(bounds, Color::BLACK);
+    }
+
+    Transaction().setBuffer(layer, nullptr).apply();
+    {
+        SCOPED_TRACE("null buffer has no effect");
+        auto shot = getScreenCapture();
+        shot->expectColor(bounds, Color::BLACK);
+    }
+}
+
 } // namespace android
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/ScreenCapture_test.cpp b/services/surfaceflinger/tests/ScreenCapture_test.cpp
index 976ee35..013694f 100644
--- a/services/surfaceflinger/tests/ScreenCapture_test.cpp
+++ b/services/surfaceflinger/tests/ScreenCapture_test.cpp
@@ -222,6 +222,14 @@
     mCapture->checkPixel(0, 0, 200, 200, 200);
 }
 
+TEST_F(ScreenCaptureTest, CaptureLayerExcludeThroughDisplayArgs) {
+    mCaptureArgs.excludeHandles = {mFGSurfaceControl->getHandle()};
+    ScreenCapture::captureDisplay(&mCapture, mCaptureArgs);
+    mCapture->expectBGColor(0, 0);
+    // Doesn't capture FG layer which is at 64, 64
+    mCapture->expectBGColor(64, 64);
+}
+
 // Like the last test but verifies that children are also exclude.
 TEST_F(ScreenCaptureTest, CaptureLayerExcludeTree) {
     auto fgHandle = mFGSurfaceControl->getHandle();
diff --git a/services/surfaceflinger/tests/WindowInfosListener_test.cpp b/services/surfaceflinger/tests/WindowInfosListener_test.cpp
index d71486f..ad9a674 100644
--- a/services/surfaceflinger/tests/WindowInfosListener_test.cpp
+++ b/services/surfaceflinger/tests/WindowInfosListener_test.cpp
@@ -16,13 +16,17 @@
 
 #include <gtest/gtest.h>
 #include <gui/SurfaceComposerClient.h>
+#include <gui/WindowInfosUpdate.h>
 #include <private/android_filesystem_config.h>
+#include <cstdint>
 #include <future>
+#include "utils/WindowInfosListenerUtils.h"
 
 namespace android {
 using Transaction = SurfaceComposerClient::Transaction;
 using gui::DisplayInfo;
 using gui::WindowInfo;
+constexpr auto findMatchingWindowInfo = WindowInfosListenerUtils::findMatchingWindowInfo;
 
 using WindowInfosPredicate = std::function<bool(const std::vector<WindowInfo>&)>;
 
@@ -35,46 +39,14 @@
 
     void TearDown() override { seteuid(AID_ROOT); }
 
-    struct WindowInfosListener : public gui::WindowInfosListener {
-    public:
-        WindowInfosListener(WindowInfosPredicate predicate, std::promise<void>& promise)
-              : mPredicate(std::move(predicate)), mPromise(promise) {}
-
-        void onWindowInfosChanged(const std::vector<WindowInfo>& windowInfos,
-                                  const std::vector<DisplayInfo>&) override {
-            if (mPredicate(windowInfos)) {
-                mPromise.set_value();
-            }
-        }
-
-    private:
-        WindowInfosPredicate mPredicate;
-        std::promise<void>& mPromise;
-    };
-
     sp<SurfaceComposerClient> mClient;
+    WindowInfosListenerUtils mWindowInfosListenerUtils;
 
-    bool waitForWindowInfosPredicate(WindowInfosPredicate predicate) {
-        std::promise<void> promise;
-        auto listener = sp<WindowInfosListener>::make(std::move(predicate), promise);
-        mClient->addWindowInfosListener(listener);
-        auto future = promise.get_future();
-        bool satisfied = future.wait_for(std::chrono::seconds{1}) == std::future_status::ready;
-        mClient->removeWindowInfosListener(listener);
-        return satisfied;
+    bool waitForWindowInfosPredicate(const WindowInfosPredicate& predicate) {
+        return mWindowInfosListenerUtils.waitForWindowInfosPredicate(std::move(predicate));
     }
 };
 
-std::optional<WindowInfo> findMatchingWindowInfo(WindowInfo targetWindowInfo,
-                                                 std::vector<WindowInfo> windowInfos) {
-    for (WindowInfo windowInfo : windowInfos) {
-        if (windowInfo.token == targetWindowInfo.token) {
-            return windowInfo;
-        }
-    }
-    return std::nullopt;
-}
-
 TEST_F(WindowInfosListenerTest, WindowInfoAddedAndRemoved) {
     std::string name = "Test Layer";
     sp<IBinder> token = sp<BBinder>::make();
@@ -93,14 +65,14 @@
             .apply();
 
     auto windowPresent = [&](const std::vector<WindowInfo>& windowInfos) {
-        return findMatchingWindowInfo(windowInfo, windowInfos).has_value();
+        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();
+        return !findMatchingWindowInfo(windowInfo, windowInfos);
     };
     ASSERT_TRUE(waitForWindowInfosPredicate(windowNotPresent));
 }
@@ -132,8 +104,7 @@
     };
     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 =
@@ -142,7 +113,10 @@
                 if (!foundWindowInfo) {
                     return false;
                 }
-                return foundWindowInfo->touchableRegion.hasSameRects(windowInfo.touchableRegion);
+
+                auto touchableRegion =
+                        foundWindowInfo->transform.transform(foundWindowInfo->touchableRegion);
+                return touchableRegion.hasSameRects(windowInfo.touchableRegion);
             };
     ASSERT_TRUE(waitForWindowInfosPredicate(windowIsPresentAndTouchableRegionMatches));
 }
diff --git a/services/surfaceflinger/tests/tracing/Android.bp b/services/surfaceflinger/tests/tracing/Android.bp
index aa6c74e..21ebaea 100644
--- a/services/surfaceflinger/tests/tracing/Android.bp
+++ b/services/surfaceflinger/tests/tracing/Android.bp
@@ -30,7 +30,7 @@
     ],
     test_suites: ["device-tests"],
     sanitize: {
-        address: false,
+        address: true,
     },
     srcs: [
         ":libsurfaceflinger_sources",
diff --git a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
index 5f9214c..2b29530 100644
--- a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
+++ b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
@@ -20,7 +20,9 @@
 #include <fstream>
 #include <iostream>
 #include <string>
+#include <unordered_map>
 
+#include <LayerProtoHelper.h>
 #include <LayerTraceGenerator.h>
 #include <Tracing/TransactionProtoParser.h>
 #include <layerproto/LayerProtoHeader.h>
@@ -84,20 +86,23 @@
 std::vector<std::filesystem::path> TransactionTraceTestSuite::sTransactionTraces{};
 
 struct LayerInfo {
-    int id;
+    int32_t id;
     std::string name;
-    int parent;
+    int32_t parent;
     int z;
     uint64_t curr_frame;
     float x;
     float y;
     uint32_t bufferWidth;
     uint32_t bufferHeight;
+    Rect touchableRegionBounds;
 };
 
 bool operator==(const LayerInfo& lh, const LayerInfo& rh) {
-    return std::make_tuple(lh.id, lh.name, lh.parent, lh.z, lh.curr_frame) ==
-            std::make_tuple(rh.id, rh.name, rh.parent, rh.z, rh.curr_frame);
+    return std::make_tuple(lh.id, lh.name, lh.parent, lh.z, lh.curr_frame, lh.bufferWidth,
+                           lh.bufferHeight, lh.touchableRegionBounds) ==
+            std::make_tuple(rh.id, rh.name, rh.parent, rh.z, rh.curr_frame, rh.bufferWidth,
+                            rh.bufferHeight, rh.touchableRegionBounds);
 }
 
 bool compareById(const LayerInfo& a, const LayerInfo& b) {
@@ -108,7 +113,9 @@
     *os << "Layer [" << info.id << "] name=" << info.name << " parent=" << info.parent
         << " z=" << info.z << " curr_frame=" << info.curr_frame << " x=" << info.x
         << " y=" << info.y << " bufferWidth=" << info.bufferWidth
-        << " bufferHeight=" << info.bufferHeight;
+        << " bufferHeight=" << info.bufferHeight << "touchableRegionBounds={"
+        << info.touchableRegionBounds.left << "," << info.touchableRegionBounds.top << ","
+        << info.touchableRegionBounds.right << "," << info.touchableRegionBounds.bottom << "}";
 }
 
 struct find_id : std::unary_function<LayerInfo, bool> {
@@ -118,6 +125,17 @@
 };
 
 static LayerInfo getLayerInfoFromProto(::android::surfaceflinger::LayerProto& proto) {
+    Rect touchableRegionBounds = Rect::INVALID_RECT;
+    // ignore touchable region for layers without buffers, the new fe aggressively avoids
+    // calculating state for layers that are not visible which could lead to mismatches
+    if (proto.has_input_window_info() && proto.input_window_info().has_touchable_region() &&
+        proto.has_active_buffer()) {
+        Region touchableRegion;
+        LayerProtoHelper::readFromProto(proto.input_window_info().touchable_region(),
+                                        touchableRegion);
+        touchableRegionBounds = touchableRegion.bounds();
+    }
+
     return {proto.id(),
             proto.name(),
             proto.parent(),
@@ -126,7 +144,37 @@
             proto.has_position() ? proto.position().x() : -1,
             proto.has_position() ? proto.position().y() : -1,
             proto.has_active_buffer() ? proto.active_buffer().width() : 0,
-            proto.has_active_buffer() ? proto.active_buffer().height() : 0};
+            proto.has_active_buffer() ? proto.active_buffer().height() : 0,
+            touchableRegionBounds};
+}
+
+static std::vector<LayerInfo> getLayerInfosFromProto(
+        android::surfaceflinger::LayersTraceProto& entry) {
+    std::unordered_map<int32_t /* snapshotId*/, int32_t /*layerId*/> snapshotIdToLayerId;
+    std::vector<LayerInfo> layers;
+    layers.reserve(static_cast<size_t>(entry.layers().layers_size()));
+    bool mapSnapshotIdToLayerId = false;
+    for (int i = 0; i < entry.layers().layers_size(); i++) {
+        auto layer = entry.layers().layers(i);
+        LayerInfo layerInfo = getLayerInfoFromProto(layer);
+
+        snapshotIdToLayerId[layerInfo.id] = static_cast<int32_t>(layer.original_id());
+        if (layer.original_id() != 0) {
+            mapSnapshotIdToLayerId = true;
+        }
+        layers.push_back(layerInfo);
+    }
+    std::sort(layers.begin(), layers.end(), compareById);
+
+    if (!mapSnapshotIdToLayerId) {
+        return layers;
+    }
+    for (auto& layer : layers) {
+        layer.id = snapshotIdToLayerId[layer.id];
+        auto it = snapshotIdToLayerId.find(layer.parent);
+        layer.parent = it == snapshotIdToLayerId.end() ? -1 : it->second;
+    }
+    return layers;
 }
 
 TEST_P(TransactionTraceTestSuite, validateEndState) {
@@ -139,21 +187,9 @@
 
     EXPECT_EQ(expectedLastEntry.layers().layers_size(), actualLastEntry.layers().layers_size());
 
-    std::vector<LayerInfo> expectedLayers;
-    expectedLayers.reserve(static_cast<size_t>(expectedLastEntry.layers().layers_size()));
-    for (int i = 0; i < expectedLastEntry.layers().layers_size(); i++) {
-        auto layer = expectedLastEntry.layers().layers(i);
-        expectedLayers.push_back(getLayerInfoFromProto(layer));
-    }
-    std::sort(expectedLayers.begin(), expectedLayers.end(), compareById);
-
-    std::vector<LayerInfo> actualLayers;
-    actualLayers.reserve(static_cast<size_t>(actualLastEntry.layers().layers_size()));
-    for (int i = 0; i < actualLastEntry.layers().layers_size(); i++) {
-        auto layer = actualLastEntry.layers().layers(i);
-        actualLayers.push_back(getLayerInfoFromProto(layer));
-    }
-    std::sort(actualLayers.begin(), actualLayers.end(), compareById);
+    std::vector<LayerInfo> expectedLayers = getLayerInfosFromProto(expectedLastEntry);
+    std::vector<LayerInfo> actualLayers = getLayerInfosFromProto(actualLastEntry);
+    ;
 
     size_t i = 0;
     for (; i < actualLayers.size() && i < expectedLayers.size(); i++) {
diff --git a/services/surfaceflinger/tests/tracing/testdata/layers_trace_b275630566.winscope b/services/surfaceflinger/tests/tracing/testdata/layers_trace_b275630566.winscope
new file mode 100644
index 0000000..fe504d7
--- /dev/null
+++ b/services/surfaceflinger/tests/tracing/testdata/layers_trace_b275630566.winscope
Binary files differ
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_b275630566.winscope b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_b275630566.winscope
new file mode 100644
index 0000000..6f7ba15
--- /dev/null
+++ b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_b275630566.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/ActiveDisplayRotationFlagsTest.cpp b/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp
new file mode 100644
index 0000000..7077523
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include "DisplayTransactionTestHelpers.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace android {
+namespace {
+
+struct ActiveDisplayRotationFlagsTest : DisplayTransactionTest {
+    static constexpr bool kWithMockScheduler = false;
+    ActiveDisplayRotationFlagsTest() : DisplayTransactionTest(kWithMockScheduler) {}
+
+    void SetUp() override {
+        injectMockScheduler(kInnerDisplayId);
+
+        // Inject inner and outer displays with uninitialized power modes.
+        constexpr bool kInitPowerMode = false;
+        {
+            InnerDisplayVariant::injectHwcDisplay<kInitPowerMode>(this);
+            auto injector = InnerDisplayVariant::makeFakeExistingDisplayInjector(this);
+            injector.setPowerMode(std::nullopt);
+            injector.setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector());
+            mInnerDisplay = injector.inject();
+        }
+        {
+            OuterDisplayVariant::injectHwcDisplay<kInitPowerMode>(this);
+            auto injector = OuterDisplayVariant::makeFakeExistingDisplayInjector(this);
+            injector.setPowerMode(std::nullopt);
+            mOuterDisplay = injector.inject();
+        }
+
+        mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::ON);
+        mFlinger.setPowerModeInternal(mOuterDisplay, PowerMode::ON);
+
+        // The flags are a static variable, so by modifying them in the test, we
+        // are modifying the real ones used by SurfaceFlinger. Save the original
+        // flags so we can restore them on teardown. This isn't perfect - the
+        // phone may have been rotated during the test, so we're restoring the
+        // wrong flags. But if the phone is rotated, this may also fail the test.
+        mOldRotationFlags = mFlinger.mutableActiveDisplayRotationFlags();
+
+        // Reset to the expected default state.
+        mFlinger.mutableActiveDisplayRotationFlags() = ui::Transform::ROT_0;
+    }
+
+    void TearDown() override { mFlinger.mutableActiveDisplayRotationFlags() = mOldRotationFlags; }
+
+    static inline PhysicalDisplayId kInnerDisplayId = InnerDisplayVariant::DISPLAY_ID::get();
+    static inline PhysicalDisplayId kOuterDisplayId = OuterDisplayVariant::DISPLAY_ID::get();
+
+    sp<DisplayDevice> mInnerDisplay, mOuterDisplay;
+    ui::Transform::RotationFlags mOldRotationFlags;
+};
+
+TEST_F(ActiveDisplayRotationFlagsTest, defaultRotation) {
+    ASSERT_EQ(ui::Transform::ROT_0, SurfaceFlinger::getActiveDisplayRotationFlags());
+}
+
+TEST_F(ActiveDisplayRotationFlagsTest, rotate90) {
+    auto displayToken = mInnerDisplay->getDisplayToken().promote();
+    mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+    mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+            ui::ROTATION_90;
+
+    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+    ASSERT_EQ(ui::Transform::ROT_90, SurfaceFlinger::getActiveDisplayRotationFlags());
+}
+
+TEST_F(ActiveDisplayRotationFlagsTest, rotate90_inactive) {
+    auto displayToken = mOuterDisplay->getDisplayToken().promote();
+    mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+    mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+            ui::ROTATION_90;
+
+    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+    ASSERT_EQ(ui::Transform::ROT_0, SurfaceFlinger::getActiveDisplayRotationFlags());
+}
+
+TEST_F(ActiveDisplayRotationFlagsTest, rotateBoth_innerActive) {
+    auto displayToken = mInnerDisplay->getDisplayToken().promote();
+    mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+    mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+            ui::ROTATION_180;
+
+    displayToken = mOuterDisplay->getDisplayToken().promote();
+    mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+    mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+            ui::ROTATION_270;
+
+    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+    ASSERT_EQ(ui::Transform::ROT_180, SurfaceFlinger::getActiveDisplayRotationFlags());
+}
+
+TEST_F(ActiveDisplayRotationFlagsTest, rotateBoth_outerActive) {
+    mFlinger.mutableActiveDisplayId() = kOuterDisplayId;
+    auto displayToken = mInnerDisplay->getDisplayToken().promote();
+    mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+    mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+            ui::ROTATION_180;
+
+    displayToken = mOuterDisplay->getDisplayToken().promote();
+    mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+    mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+            ui::ROTATION_270;
+
+    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+    ASSERT_EQ(ui::Transform::ROT_270, SurfaceFlinger::getActiveDisplayRotationFlags());
+}
+
+TEST_F(ActiveDisplayRotationFlagsTest, onActiveDisplayChanged) {
+    auto displayToken = mInnerDisplay->getDisplayToken().promote();
+    mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+    mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+            ui::ROTATION_180;
+
+    displayToken = mOuterDisplay->getDisplayToken().promote();
+    mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+    mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+            ui::ROTATION_270;
+
+    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+    ASSERT_EQ(ui::Transform::ROT_180, SurfaceFlinger::getActiveDisplayRotationFlags());
+
+    mFlinger.onActiveDisplayChanged(mInnerDisplay.get(), *mOuterDisplay);
+    ASSERT_EQ(ui::Transform::ROT_270, SurfaceFlinger::getActiveDisplayRotationFlags());
+}
+
+} // namespace
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp b/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp
deleted file mode 100644
index 513f779..0000000
--- a/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "AidlPowerHalWrapperTest"
-
-#include <android-base/stringprintf.h>
-#include <android/hardware/power/IPower.h>
-#include <android/hardware/power/IPowerHintSession.h>
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include <algorithm>
-#include <chrono>
-#include <memory>
-#include "DisplayHardware/PowerAdvisor.h"
-#include "android/hardware/power/WorkDuration.h"
-#include "binder/Status.h"
-#include "log/log_main.h"
-#include "mock/DisplayHardware/MockIPower.h"
-#include "mock/DisplayHardware/MockIPowerHintSession.h"
-#include "utils/Timers.h"
-
-using namespace android;
-using namespace android::Hwc2::mock;
-using namespace android::hardware::power;
-using namespace std::chrono_literals;
-using namespace testing;
-
-namespace android::Hwc2::impl {
-
-class AidlPowerHalWrapperTest : public testing::Test {
-public:
-    void SetUp() override;
-
-protected:
-    std::unique_ptr<AidlPowerHalWrapper> mWrapper = nullptr;
-    sp<NiceMock<MockIPower>> mMockHal = nullptr;
-    sp<NiceMock<MockIPowerHintSession>> mMockSession = nullptr;
-    void verifyAndClearExpectations();
-    void sendActualWorkDurationGroup(std::vector<WorkDuration> durations);
-    static constexpr std::chrono::duration kStaleTimeout = 100ms;
-};
-
-void AidlPowerHalWrapperTest::SetUp() {
-    mMockHal = sp<NiceMock<MockIPower>>::make();
-    mMockSession = sp<NiceMock<MockIPowerHintSession>>::make();
-    ON_CALL(*mMockHal.get(), getHintSessionPreferredRate(_)).WillByDefault(Return(Status::ok()));
-    mWrapper = std::make_unique<AidlPowerHalWrapper>(mMockHal);
-}
-
-void AidlPowerHalWrapperTest::verifyAndClearExpectations() {
-    Mock::VerifyAndClearExpectations(mMockHal.get());
-    Mock::VerifyAndClearExpectations(mMockSession.get());
-}
-
-void AidlPowerHalWrapperTest::sendActualWorkDurationGroup(std::vector<WorkDuration> durations) {
-    for (size_t i = 0; i < durations.size(); i++) {
-        auto duration = durations[i];
-        mWrapper->sendActualWorkDuration(Duration::fromNs(duration.durationNanos),
-                                         TimePoint::fromNs(duration.timeStampNanos));
-    }
-}
-
-WorkDuration toWorkDuration(std::chrono::nanoseconds durationNanos, int64_t timeStampNanos) {
-    WorkDuration duration;
-    duration.durationNanos = durationNanos.count();
-    duration.timeStampNanos = timeStampNanos;
-    return duration;
-}
-
-WorkDuration toWorkDuration(std::pair<std::chrono::nanoseconds, nsecs_t> timePair) {
-    return toWorkDuration(timePair.first, timePair.second);
-}
-
-std::string printWorkDurations(const ::std::vector<WorkDuration>& durations) {
-    std::ostringstream os;
-    for (auto duration : durations) {
-        os << duration.toString();
-        os << "\n";
-    }
-    return os.str();
-}
-
-namespace {
-TEST_F(AidlPowerHalWrapperTest, supportsPowerHintSession) {
-    ASSERT_TRUE(mWrapper->supportsPowerHintSession());
-    Mock::VerifyAndClearExpectations(mMockHal.get());
-    ON_CALL(*mMockHal.get(), getHintSessionPreferredRate(_))
-            .WillByDefault(Return(Status::fromExceptionCode(Status::Exception::EX_ILLEGAL_STATE)));
-    auto newWrapper = AidlPowerHalWrapper(mMockHal);
-    EXPECT_FALSE(newWrapper.supportsPowerHintSession());
-}
-
-TEST_F(AidlPowerHalWrapperTest, startPowerHintSession) {
-    ASSERT_TRUE(mWrapper->supportsPowerHintSession());
-    std::vector<int32_t> threadIds = {1, 2};
-    mWrapper->setPowerHintSessionThreadIds(threadIds);
-    EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
-            .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
-    EXPECT_TRUE(mWrapper->startPowerHintSession());
-    EXPECT_FALSE(mWrapper->startPowerHintSession());
-}
-
-TEST_F(AidlPowerHalWrapperTest, restartNewPowerHintSessionWithNewThreadIds) {
-    ASSERT_TRUE(mWrapper->supportsPowerHintSession());
-
-    std::vector<int32_t> threadIds = {1, 2};
-    EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
-            .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
-    mWrapper->setPowerHintSessionThreadIds(threadIds);
-    EXPECT_EQ(mWrapper->getPowerHintSessionThreadIds(), threadIds);
-    ASSERT_TRUE(mWrapper->startPowerHintSession());
-    verifyAndClearExpectations();
-
-    threadIds = {2, 3};
-    EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
-            .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
-    EXPECT_CALL(*mMockSession.get(), close()).Times(1);
-    mWrapper->setPowerHintSessionThreadIds(threadIds);
-    EXPECT_EQ(mWrapper->getPowerHintSessionThreadIds(), threadIds);
-    verifyAndClearExpectations();
-
-    EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _)).Times(0);
-    EXPECT_CALL(*mMockSession.get(), close()).Times(0);
-    mWrapper->setPowerHintSessionThreadIds(threadIds);
-    verifyAndClearExpectations();
-}
-
-TEST_F(AidlPowerHalWrapperTest, setTargetWorkDuration) {
-    ASSERT_TRUE(mWrapper->supportsPowerHintSession());
-
-    std::vector<int32_t> threadIds = {1, 2};
-    mWrapper->setPowerHintSessionThreadIds(threadIds);
-    EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
-            .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
-    ASSERT_TRUE(mWrapper->startPowerHintSession());
-    verifyAndClearExpectations();
-
-    std::chrono::nanoseconds base = 100ms;
-    // test cases with target work duration and whether it should update hint against baseline 100ms
-    const std::vector<std::pair<std::chrono::nanoseconds, bool>> testCases =
-            {{0ms, true}, {-1ms, true}, {200ms, true}, {2ms, true}, {100ms, false}, {109ms, true}};
-
-    for (const auto& test : testCases) {
-        // reset to 100ms baseline
-        mWrapper->setTargetWorkDuration(1ns);
-        mWrapper->setTargetWorkDuration(base);
-
-        std::chrono::nanoseconds target = test.first;
-        EXPECT_CALL(*mMockSession.get(), updateTargetWorkDuration(target.count()))
-                .Times(test.second ? 1 : 0);
-        mWrapper->setTargetWorkDuration(target);
-        verifyAndClearExpectations();
-    }
-}
-
-TEST_F(AidlPowerHalWrapperTest, setTargetWorkDuration_shouldReconnectOnError) {
-    ASSERT_TRUE(mWrapper->supportsPowerHintSession());
-
-    std::vector<int32_t> threadIds = {1, 2};
-    mWrapper->setPowerHintSessionThreadIds(threadIds);
-    EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
-            .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
-    ASSERT_TRUE(mWrapper->startPowerHintSession());
-    verifyAndClearExpectations();
-
-    EXPECT_CALL(*mMockSession.get(), updateTargetWorkDuration(1))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_ILLEGAL_STATE)));
-    mWrapper->setTargetWorkDuration(1ns);
-    EXPECT_TRUE(mWrapper->shouldReconnectHAL());
-}
-
-TEST_F(AidlPowerHalWrapperTest, sendActualWorkDuration) {
-    ASSERT_TRUE(mWrapper->supportsPowerHintSession());
-
-    std::vector<int32_t> threadIds = {1, 2};
-    mWrapper->setPowerHintSessionThreadIds(threadIds);
-    EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
-            .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
-    ASSERT_TRUE(mWrapper->startPowerHintSession());
-    verifyAndClearExpectations();
-
-    auto base = toWorkDuration(100ms, 0);
-    // test cases with actual work durations and whether it should update hint against baseline
-    // 100ms
-    const std::vector<std::pair<std::vector<std::pair<std::chrono::nanoseconds, nsecs_t>>, bool>>
-            testCases = {{{{-1ms, 100}}, false},
-                         {{{50ms, 100}}, true},
-                         {{{100ms, 100}, {200ms, 200}}, true},
-                         {{{100ms, 500}, {100ms, 600}, {3ms, 600}}, true}};
-
-    for (const auto& test : testCases) {
-        // reset actual duration
-        sendActualWorkDurationGroup({base});
-
-        auto raw = test.first;
-        std::vector<WorkDuration> durations(raw.size());
-        std::transform(raw.begin(), raw.end(), durations.begin(),
-                       [](auto d) { return toWorkDuration(d); });
-        for (auto& duration : durations) {
-            EXPECT_CALL(*mMockSession.get(),
-                        reportActualWorkDuration(std::vector<WorkDuration>{duration}))
-                    .Times(test.second ? 1 : 0);
-        }
-        sendActualWorkDurationGroup(durations);
-        verifyAndClearExpectations();
-    }
-}
-
-TEST_F(AidlPowerHalWrapperTest, sendActualWorkDuration_shouldReconnectOnError) {
-    ASSERT_TRUE(mWrapper->supportsPowerHintSession());
-
-    std::vector<int32_t> threadIds = {1, 2};
-    mWrapper->setPowerHintSessionThreadIds(threadIds);
-    EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
-            .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
-    ASSERT_TRUE(mWrapper->startPowerHintSession());
-    verifyAndClearExpectations();
-    WorkDuration duration;
-    duration.durationNanos = 1;
-    EXPECT_CALL(*mMockSession.get(), reportActualWorkDuration(_))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_ILLEGAL_STATE)));
-    sendActualWorkDurationGroup({duration});
-    EXPECT_TRUE(mWrapper->shouldReconnectHAL());
-}
-
-} // namespace
-} // namespace android::Hwc2::impl
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 012a4ad..70f8a83 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -24,7 +24,7 @@
 filegroup {
     name: "libsurfaceflinger_mock_sources",
     srcs: [
-        "mock/DisplayHardware/MockAidlPowerHalWrapper.cpp",
+        "mock/DisplayHardware/MockPowerHalController.cpp",
         "mock/DisplayHardware/MockComposer.cpp",
         "mock/DisplayHardware/MockHWC2.cpp",
         "mock/DisplayHardware/MockIPower.cpp",
@@ -70,7 +70,7 @@
         ":libsurfaceflinger_mock_sources",
         ":libsurfaceflinger_sources",
         "libsurfaceflinger_unittest_main.cpp",
-        "AidlPowerHalWrapperTest.cpp",
+        "ActiveDisplayRotationFlagsTest.cpp",
         "CompositionTest.cpp",
         "DisplayIdGeneratorTest.cpp",
         "DisplayTransactionTest.cpp",
@@ -105,11 +105,12 @@
         "SurfaceFlinger_DisplayTransactionCommitTest.cpp",
         "SurfaceFlinger_ExcludeDolbyVisionTest.cpp",
         "SurfaceFlinger_GetDisplayNativePrimariesTest.cpp",
+        "SurfaceFlinger_GetDisplayStatsTest.cpp",
         "SurfaceFlinger_HdrOutputControlTest.cpp",
         "SurfaceFlinger_HotplugTest.cpp",
+        "SurfaceFlinger_InitializeDisplaysTest.cpp",
         "SurfaceFlinger_MultiDisplayPacesetterTest.cpp",
         "SurfaceFlinger_NotifyPowerBoostTest.cpp",
-        "SurfaceFlinger_OnInitializeDisplaysTest.cpp",
         "SurfaceFlinger_PowerHintTest.cpp",
         "SurfaceFlinger_SetDisplayStateTest.cpp",
         "SurfaceFlinger_SetPowerModeInternalTest.cpp",
@@ -129,6 +130,7 @@
         "TransactionTracingTest.cpp",
         "TunnelModeEnabledReporterTest.cpp",
         "StrongTypingTest.cpp",
+        "VSyncCallbackRegistrationTest.cpp",
         "VSyncDispatchTimerQueueTest.cpp",
         "VSyncDispatchRealtimeTest.cpp",
         "VsyncModulatorTest.cpp",
@@ -196,6 +198,7 @@
         "libinput",
         "liblog",
         "libnativewindow",
+        "libpowermanager",
         "libprocessgroup",
         "libprotobuf-cpp-lite",
         "libSurfaceFlingerProp",
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 19a93e1..e8a9cfe 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -200,11 +200,11 @@
     constexpr bool regionSampling = false;
 
     auto renderArea = DisplayRenderArea::create(mDisplay, sourceCrop, sourceCrop.getSize(),
-                                                ui::Dataspace::V0_SRGB, ui::Transform::ROT_0);
+                                                ui::Dataspace::V0_SRGB, true, true);
 
     auto traverseLayers = [this](const LayerVector::Visitor& visitor) {
         return mFlinger.traverseLayersInLayerStack(mDisplay->getLayerStack(),
-                                                   CaptureArgs::UNSET_UID, visitor);
+                                                   CaptureArgs::UNSET_UID, {}, visitor);
     };
 
     auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
@@ -605,7 +605,7 @@
                     EXPECT_EQ(false, layer.source.buffer.isOpaque);
                     EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.x);
                     EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.y);
-                    EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace);
+                    EXPECT_EQ(ui::Dataspace::V0_SRGB, layer.sourceDataspace);
                     EXPECT_EQ(LayerProperties::COLOR[3], layer.alpha);
                     return resultFuture;
                 });
@@ -654,7 +654,7 @@
                               layer.source.solidColor);
                     EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.x);
                     EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.y);
-                    EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace);
+                    EXPECT_EQ(ui::Dataspace::V0_SRGB, layer.sourceDataspace);
                     EXPECT_EQ(LayerProperties::COLOR[3], layer.alpha);
                     return resultFuture;
                 });
@@ -680,6 +680,9 @@
                 NativeHandle::create(reinterpret_cast<native_handle_t*>(DEFAULT_SIDEBAND_STREAM),
                                      false);
         test->mFlinger.setLayerSidebandStream(layer, stream);
+        auto& layerDrawingState = test->mFlinger.mutableLayerDrawingState(layer);
+        layerDrawingState.crop =
+                Rect(0, 0, SidebandLayerProperties::HEIGHT, SidebandLayerProperties::WIDTH);
     }
 
     static void setupHwcSetSourceCropBufferCallExpectations(CompositionTest* test) {
@@ -731,7 +734,7 @@
                     EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), layer.source.solidColor);
                     EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.x);
                     EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.y);
-                    EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace);
+                    EXPECT_EQ(ui::Dataspace::V0_SRGB, layer.sourceDataspace);
                     EXPECT_EQ(1.0f, layer.alpha);
                     return resultFuture;
                 });
@@ -814,6 +817,7 @@
         Mock::VerifyAndClear(test->mComposer);
 
         test->mFlinger.mutableDrawingState().layersSortedByZ.add(layer);
+        test->mFlinger.mutableVisibleRegionsDirty() = true;
     }
 
     static void cleanupInjectedLayers(CompositionTest* test) {
@@ -822,6 +826,7 @@
 
         test->mDisplay->getCompositionDisplay()->clearOutputLayers();
         test->mFlinger.mutableDrawingState().layersSortedByZ.clear();
+        test->mFlinger.mutablePreviouslyComposedLayers().clear();
 
         // Layer should be unregistered with scheduler.
         test->mFlinger.commit();
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index f1cdca3..5fed9b4 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -24,6 +24,7 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <log/log.h>
+#include <scheduler/VsyncConfig.h>
 #include <utils/Errors.h>
 
 #include "AsyncCallRecorder.h"
@@ -77,7 +78,7 @@
     EventThreadTest();
     ~EventThreadTest() override;
 
-    void createThread();
+    void setupEventThread(std::chrono::nanoseconds vsyncPeriod);
     sp<MockEventThreadConnection> createConnection(ConnectionEventRecorder& recorder,
                                                    EventRegistrationFlags eventRegistration = {},
                                                    uid_t ownerUid = mConnectionUid);
@@ -90,8 +91,9 @@
                                               nsecs_t expectedTimestamp, unsigned expectedCount);
     void expectVsyncEventReceivedByConnection(nsecs_t expectedTimestamp, unsigned expectedCount);
     void expectVsyncEventFrameTimelinesCorrect(
-            nsecs_t expectedTimestamp,
-            /*VSyncSource::VSyncData*/ gui::VsyncEventData::FrameTimeline preferredVsyncData);
+            nsecs_t expectedTimestamp, gui::VsyncEventData::FrameTimeline preferredVsyncData);
+    void expectVsyncEventDataFrameTimelinesValidLength(VsyncEventData vsyncEventData,
+                                                       std::chrono::nanoseconds vsyncPeriod);
     void expectHotplugEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
                                                 bool expectedConnected);
     void expectConfigChangedEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
@@ -154,19 +156,6 @@
             .WillRepeatedly(Invoke(mVSyncCallbackUpdateRecorder.getInvocable()));
     EXPECT_CALL(mockDispatch, unregisterCallback(_))
             .WillRepeatedly(Invoke(mVSyncCallbackUnregisterRecorder.getInvocable()));
-
-    createThread();
-    mConnection =
-            createConnection(mConnectionEventCallRecorder,
-                             gui::ISurfaceComposer::EventRegistration::modeChanged |
-                                     gui::ISurfaceComposer::EventRegistration::frameRateOverride);
-    mThrottledConnection = createConnection(mThrottledConnectionEventCallRecorder,
-                                            gui::ISurfaceComposer::EventRegistration::modeChanged,
-                                            mThrottledConnectionUid);
-
-    // A display must be connected for VSYNC events to be delivered.
-    mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, true);
-    expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, true);
 }
 
 EventThreadTest::~EventThreadTest() {
@@ -179,14 +168,12 @@
     EXPECT_TRUE(mVSyncCallbackUnregisterRecorder.waitForCall().has_value());
 }
 
-void EventThreadTest::createThread() {
+void EventThreadTest::setupEventThread(std::chrono::nanoseconds vsyncPeriod) {
     const auto throttleVsync = [&](nsecs_t expectedVsyncTimestamp, uid_t uid) {
         mThrottleVsyncCallRecorder.getInvocable()(expectedVsyncTimestamp, uid);
         return (uid == mThrottledConnectionUid);
     };
-    const auto getVsyncPeriod = [](uid_t uid) {
-        return VSYNC_PERIOD.count();
-    };
+    const auto getVsyncPeriod = [vsyncPeriod](uid_t uid) { return vsyncPeriod.count(); };
 
     mTokenManager = std::make_unique<frametimeline::impl::TokenManager>();
     mThread = std::make_unique<impl::EventThread>("EventThreadTest", mVsyncSchedule,
@@ -195,6 +182,18 @@
 
     // EventThread should register itself as VSyncSource callback.
     EXPECT_TRUE(mVSyncCallbackRegisterRecorder.waitForCall().has_value());
+
+    mConnection =
+            createConnection(mConnectionEventCallRecorder,
+                             gui::ISurfaceComposer::EventRegistration::modeChanged |
+                                     gui::ISurfaceComposer::EventRegistration::frameRateOverride);
+    mThrottledConnection = createConnection(mThrottledConnectionEventCallRecorder,
+                                            gui::ISurfaceComposer::EventRegistration::modeChanged,
+                                            mThrottledConnectionUid);
+
+    // A display must be connected for VSYNC events to be delivered.
+    mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, true);
+    expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, true);
 }
 
 sp<EventThreadTest::MockEventThreadConnection> EventThreadTest::createConnection(
@@ -259,7 +258,7 @@
     ASSERT_TRUE(args.has_value()) << " did not receive an event for timestamp "
                                   << expectedTimestamp;
     const auto& event = std::get<0>(args.value());
-    for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
+    for (int i = 0; i < event.vsync.vsyncData.frameTimelinesLength; i++) {
         auto prediction = mTokenManager->getPredictionsForToken(
                 event.vsync.vsyncData.frameTimelines[i].vsyncId);
         EXPECT_TRUE(prediction.has_value());
@@ -293,6 +292,21 @@
     }
 }
 
+void EventThreadTest::expectVsyncEventDataFrameTimelinesValidLength(
+        VsyncEventData vsyncEventData, std::chrono::nanoseconds vsyncPeriod) {
+    float nonPreferredTimelinesAmount =
+            scheduler::VsyncConfig::kEarlyLatchMaxThreshold / vsyncPeriod;
+    EXPECT_LE(vsyncEventData.frameTimelinesLength, nonPreferredTimelinesAmount + 1)
+            << "Amount of non-preferred frame timelines too many;"
+            << " expected presentation time will be over threshold";
+    EXPECT_LT(nonPreferredTimelinesAmount, VsyncEventData::kFrameTimelinesCapacity)
+            << "Amount of non-preferred frame timelines should be less than max capacity";
+    EXPECT_GT(static_cast<int64_t>(vsyncEventData.frameTimelinesLength), 0)
+            << "Frame timelines length should be greater than 0";
+    EXPECT_LT(vsyncEventData.preferredFrameTimelineIndex, vsyncEventData.frameTimelinesLength)
+            << "Preferred frame timeline index should be less than frame timelines length";
+}
+
 void EventThreadTest::expectHotplugEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
                                                              bool expectedConnected) {
     auto args = mConnectionEventCallRecorder.waitForCall();
@@ -343,6 +357,8 @@
  */
 
 TEST_F(EventThreadTest, canCreateAndDestroyThreadWithNoEventsSent) {
+    setupEventThread(VSYNC_PERIOD);
+
     EXPECT_FALSE(mVSyncCallbackRegisterRecorder.waitForCall(0us).has_value());
     EXPECT_FALSE(mVSyncCallbackScheduleRecorder.waitForCall(0us).has_value());
     EXPECT_FALSE(mVSyncCallbackUpdateRecorder.waitForCall(0us).has_value());
@@ -352,6 +368,8 @@
 }
 
 TEST_F(EventThreadTest, vsyncRequestIsIgnoredIfDisplayIsDisconnected) {
+    setupEventThread(VSYNC_PERIOD);
+
     mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, false);
     expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, false);
 
@@ -363,6 +381,8 @@
 }
 
 TEST_F(EventThreadTest, requestNextVsyncPostsASingleVSyncEventToTheConnection) {
+    setupEventThread(VSYNC_PERIOD);
+
     // Signal that we want the next vsync event to be posted to the connection
     mThread->requestNextVsync(mConnection);
 
@@ -394,6 +414,8 @@
 }
 
 TEST_F(EventThreadTest, requestNextVsyncEventFrameTimelinesCorrect) {
+    setupEventThread(VSYNC_PERIOD);
+
     // Signal that we want the next vsync event to be posted to the connection
     mThread->requestNextVsync(mConnection);
 
@@ -405,7 +427,34 @@
     expectVsyncEventFrameTimelinesCorrect(123, {-1, 789, 456});
 }
 
+TEST_F(EventThreadTest, requestNextVsyncEventFrameTimelinesValidLength) {
+    // The VsyncEventData should not have kFrameTimelinesCapacity amount of valid frame timelines,
+    // due to longer vsync period and kEarlyLatchMaxThreshold. Use length-2 to avoid decimal
+    // truncation (e.g. 60Hz has 16.6... ms vsync period).
+    std::chrono::nanoseconds vsyncPeriod(scheduler::VsyncConfig::kEarlyLatchMaxThreshold /
+                                         (VsyncEventData::kFrameTimelinesCapacity - 2));
+    setupEventThread(vsyncPeriod);
+
+    // Signal that we want the next vsync event to be posted to the connection
+    mThread->requestNextVsync(mConnection);
+
+    expectVSyncCallbackScheduleReceived(true);
+
+    // Use the received callback to signal a vsync event.
+    // The throttler should receive the event, as well as the connection.
+    nsecs_t expectedTimestamp = 123;
+    onVSyncEvent(expectedTimestamp, 456, 789);
+
+    auto args = mConnectionEventCallRecorder.waitForCall();
+    ASSERT_TRUE(args.has_value()) << " did not receive an event for timestamp "
+                                  << expectedTimestamp;
+    const VsyncEventData vsyncEventData = std::get<0>(args.value()).vsync.vsyncData;
+    expectVsyncEventDataFrameTimelinesValidLength(vsyncEventData, vsyncPeriod);
+}
+
 TEST_F(EventThreadTest, getLatestVsyncEventData) {
+    setupEventThread(VSYNC_PERIOD);
+
     const nsecs_t now = systemTime();
     const nsecs_t preferredExpectedPresentationTime = now + 20000000;
     const nsecs_t preferredDeadline = preferredExpectedPresentationTime - kReadyDuration.count();
@@ -420,9 +469,10 @@
     // Check EventThread immediately requested a resync.
     EXPECT_TRUE(mResyncCallRecorder.waitForCall().has_value());
 
+    expectVsyncEventDataFrameTimelinesValidLength(vsyncEventData, VSYNC_PERIOD);
     EXPECT_GT(vsyncEventData.frameTimelines[0].deadlineTimestamp, now)
             << "Deadline timestamp should be greater than frame time";
-    for (size_t i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
+    for (size_t i = 0; i < vsyncEventData.frameTimelinesLength; i++) {
         auto prediction =
                 mTokenManager->getPredictionsForToken(vsyncEventData.frameTimelines[i].vsyncId);
         EXPECT_TRUE(prediction.has_value());
@@ -458,6 +508,8 @@
 }
 
 TEST_F(EventThreadTest, setVsyncRateZeroPostsNoVSyncEventsToThatConnection) {
+    setupEventThread(VSYNC_PERIOD);
+
     // Create a first connection, register it, and request a vsync rate of zero.
     ConnectionEventRecorder firstConnectionEventRecorder{0};
     sp<MockEventThreadConnection> firstConnection = createConnection(firstConnectionEventRecorder);
@@ -485,6 +537,8 @@
 }
 
 TEST_F(EventThreadTest, setVsyncRateOnePostsAllEventsToThatConnection) {
+    setupEventThread(VSYNC_PERIOD);
+
     mThread->setVsyncRate(1, mConnection);
 
     // EventThread should enable vsync callbacks.
@@ -508,6 +562,8 @@
 }
 
 TEST_F(EventThreadTest, setVsyncRateTwoPostsEveryOtherEventToThatConnection) {
+    setupEventThread(VSYNC_PERIOD);
+
     mThread->setVsyncRate(2, mConnection);
 
     // EventThread should enable vsync callbacks.
@@ -534,6 +590,8 @@
 }
 
 TEST_F(EventThreadTest, connectionsRemovedIfInstanceDestroyed) {
+    setupEventThread(VSYNC_PERIOD);
+
     mThread->setVsyncRate(1, mConnection);
 
     // EventThread should enable vsync callbacks.
@@ -551,6 +609,8 @@
 }
 
 TEST_F(EventThreadTest, connectionsRemovedIfEventDeliveryError) {
+    setupEventThread(VSYNC_PERIOD);
+
     ConnectionEventRecorder errorConnectionEventRecorder{NO_MEMORY};
     sp<MockEventThreadConnection> errorConnection = createConnection(errorConnectionEventRecorder);
     mThread->setVsyncRate(1, errorConnection);
@@ -575,6 +635,8 @@
 }
 
 TEST_F(EventThreadTest, tracksEventConnections) {
+    setupEventThread(VSYNC_PERIOD);
+
     EXPECT_EQ(2, mThread->getEventThreadConnectionCount());
     ConnectionEventRecorder errorConnectionEventRecorder{NO_MEMORY};
     sp<MockEventThreadConnection> errorConnection = createConnection(errorConnectionEventRecorder);
@@ -598,6 +660,8 @@
 }
 
 TEST_F(EventThreadTest, eventsDroppedIfNonfatalEventDeliveryError) {
+    setupEventThread(VSYNC_PERIOD);
+
     ConnectionEventRecorder errorConnectionEventRecorder{WOULD_BLOCK};
     sp<MockEventThreadConnection> errorConnection = createConnection(errorConnectionEventRecorder);
     mThread->setVsyncRate(1, errorConnection);
@@ -622,31 +686,43 @@
 }
 
 TEST_F(EventThreadTest, setPhaseOffsetForwardsToVSyncSource) {
+    setupEventThread(VSYNC_PERIOD);
+
     mThread->setDuration(321ns, 456ns);
     expectVSyncSetDurationCallReceived(321ns, 456ns);
 }
 
 TEST_F(EventThreadTest, postHotplugInternalDisconnect) {
+    setupEventThread(VSYNC_PERIOD);
+
     mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, false);
     expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, false);
 }
 
 TEST_F(EventThreadTest, postHotplugInternalConnect) {
+    setupEventThread(VSYNC_PERIOD);
+
     mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, true);
     expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, true);
 }
 
 TEST_F(EventThreadTest, postHotplugExternalDisconnect) {
+    setupEventThread(VSYNC_PERIOD);
+
     mThread->onHotplugReceived(EXTERNAL_DISPLAY_ID, false);
     expectHotplugEventReceivedByConnection(EXTERNAL_DISPLAY_ID, false);
 }
 
 TEST_F(EventThreadTest, postHotplugExternalConnect) {
+    setupEventThread(VSYNC_PERIOD);
+
     mThread->onHotplugReceived(EXTERNAL_DISPLAY_ID, true);
     expectHotplugEventReceivedByConnection(EXTERNAL_DISPLAY_ID, true);
 }
 
 TEST_F(EventThreadTest, postConfigChangedPrimary) {
+    setupEventThread(VSYNC_PERIOD);
+
     const auto mode = DisplayMode::Builder(hal::HWConfigId(0))
                               .setPhysicalDisplayId(INTERNAL_DISPLAY_ID)
                               .setId(DisplayModeId(7))
@@ -659,6 +735,8 @@
 }
 
 TEST_F(EventThreadTest, postConfigChangedExternal) {
+    setupEventThread(VSYNC_PERIOD);
+
     const auto mode = DisplayMode::Builder(hal::HWConfigId(0))
                               .setPhysicalDisplayId(EXTERNAL_DISPLAY_ID)
                               .setId(DisplayModeId(5))
@@ -671,6 +749,8 @@
 }
 
 TEST_F(EventThreadTest, postConfigChangedPrimary64bit) {
+    setupEventThread(VSYNC_PERIOD);
+
     const auto mode = DisplayMode::Builder(hal::HWConfigId(0))
                               .setPhysicalDisplayId(DISPLAY_ID_64BIT)
                               .setId(DisplayModeId(7))
@@ -682,6 +762,8 @@
 }
 
 TEST_F(EventThreadTest, suppressConfigChanged) {
+    setupEventThread(VSYNC_PERIOD);
+
     ConnectionEventRecorder suppressConnectionEventRecorder{0};
     sp<MockEventThreadConnection> suppressConnection =
             createConnection(suppressConnectionEventRecorder);
@@ -701,6 +783,8 @@
 }
 
 TEST_F(EventThreadTest, postUidFrameRateMapping) {
+    setupEventThread(VSYNC_PERIOD);
+
     const std::vector<FrameRateOverride> overrides = {
             {.uid = 1, .frameRateHz = 20},
             {.uid = 3, .frameRateHz = 40},
@@ -712,6 +796,8 @@
 }
 
 TEST_F(EventThreadTest, suppressUidFrameRateMapping) {
+    setupEventThread(VSYNC_PERIOD);
+
     const std::vector<FrameRateOverride> overrides = {
             {.uid = 1, .frameRateHz = 20},
             {.uid = 3, .frameRateHz = 40},
@@ -730,6 +816,8 @@
 }
 
 TEST_F(EventThreadTest, requestNextVsyncWithThrottleVsyncDoesntPostVSync) {
+    setupEventThread(VSYNC_PERIOD);
+
     // Signal that we want the next vsync event to be posted to the throttled connection
     mThread->requestNextVsync(mThrottledConnection);
 
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index abd7789..d26ef3c 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -489,7 +489,7 @@
 
     auto displayFrame0 = getDisplayFrame(0);
     EXPECT_EQ(displayFrame0->getActuals().presentTime, 59);
-    EXPECT_EQ(displayFrame0->getJankType(), JankType::Unknown);
+    EXPECT_EQ(displayFrame0->getJankType(), JankType::Unknown | JankType::DisplayHAL);
     EXPECT_EQ(surfaceFrame1->getActuals().presentTime, -1);
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::Unknown);
 }
@@ -2259,6 +2259,7 @@
     mFrameTimeline->setSfWakeUp(sfToken3, 72, Fps::fromPeriodNsecs(11));
     mFrameTimeline->setSfPresent(80, validPresentFence);
 
+    erroneousPresentFence2->signalForTest(2);
     validPresentFence->signalForTest(80);
 
     addEmptyDisplayFrame();
@@ -2268,14 +2269,14 @@
         EXPECT_EQ(displayFrame->getActuals().presentTime, 26);
         EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::UnknownPresent);
         EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::UnknownFinish);
-        EXPECT_EQ(displayFrame->getJankType(), JankType::Unknown);
+        EXPECT_EQ(displayFrame->getJankType(), JankType::Unknown | JankType::DisplayHAL);
     }
     {
         auto displayFrame = getDisplayFrame(1);
         EXPECT_EQ(displayFrame->getActuals().presentTime, 60);
         EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::UnknownPresent);
         EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::UnknownFinish);
-        EXPECT_EQ(displayFrame->getJankType(), JankType::Unknown);
+        EXPECT_EQ(displayFrame->getJankType(), JankType::Unknown | JankType::DisplayHAL);
     }
     {
         auto displayFrame = getDisplayFrame(2);
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
index 77dc868..e4f49e8 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};
@@ -468,7 +466,8 @@
 
     updateBackgroundColor(1, 0.5);
     UPDATE_AND_VERIFY(hierarchyBuilder);
-    auto bgLayerId = LayerCreationArgs::getInternalLayerId(1);
+    auto hierarchy = hierarchyBuilder.getPartialHierarchy(1, /*childrenOnly=*/true);
+    auto bgLayerId = hierarchy.mChildren.front().first->getLayer()->id;
     std::vector<uint32_t> expectedTraversalPath = {1,   bgLayerId, 11,   111, 12,
                                                    121, 122,       1221, 13,  2};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
@@ -664,10 +663,9 @@
     mLifecycleManager.commitChanges();
     LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
     UPDATE_AND_VERIFY(hierarchyBuilder);
-    std::vector<uint32_t> expectedTraversalPath = {1, 11, 2, 21};
+    std::vector<uint32_t> expectedTraversalPath = {2, 21, 1, 11};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
 
-    expectedTraversalPath = {1, 11, 2, 21};
     EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
     expectedTraversalPath = {};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
@@ -680,8 +678,8 @@
     setLayerStack(3, 1);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
-    std::vector<uint32_t> expected = {3, 1,  11,  111, 12,  121, 122,  1221, 13, 2,
-                                      1, 11, 111, 12,  121, 122, 1221, 13,   2};
+    std::vector<uint32_t> expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 3,
+                                      1, 11, 111, 12, 121, 122, 1221, 13, 2};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected);
     EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected);
     expected = {};
@@ -695,7 +693,7 @@
     setLayerStack(3, 1);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
-    std::vector<uint32_t> expected = {3, 1, 11, 111, 12, 121, 122, 1221, 13, 2};
+    std::vector<uint32_t> expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 3};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected);
     EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected);
     expected = {};
@@ -712,8 +710,8 @@
     createRootLayer(4);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
-    std::vector<uint32_t> expected = {3, 1,  11,  111, 12,  121, 122,  1221, 13, 2, 4,
-                                      1, 11, 111, 12,  121, 122, 1221, 13,   2,  4};
+    std::vector<uint32_t> expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 4, 3,
+                                      1, 11, 111, 12, 121, 122, 1221, 13, 2, 4};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected);
     EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected);
     expected = {};
@@ -731,7 +729,7 @@
     destroyLayerHandle(1);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
-    std::vector<uint32_t> expected = {3, 2, 2};
+    std::vector<uint32_t> expected = {2, 3, 2};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected);
     EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected);
     expected = {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..4301186 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();
@@ -290,8 +278,37 @@
         mLifecycleManager.applyTransactions(transactions);
     }
 
+    void setTouchableRegionCrop(uint32_t id, Region region, uint32_t touchCropId,
+                                bool replaceTouchableRegionWithCrop) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eInputInfoChanged;
+        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();
+        inputInfo->touchableRegion = region;
+        inputInfo->replaceTouchableRegionWithCrop = replaceTouchableRegionWithCrop;
+        transactions.back().states.front().touchCropId = touchCropId;
+
+        inputInfo->token = sp<BBinder>::make();
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setBackgroundBlurRadius(uint32_t id, uint32_t backgroundBlurRadius) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eBackgroundBlurRadiusChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.backgroundBlurRadius = backgroundBlurRadius;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
     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 b767276..85d86a7 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -84,7 +84,7 @@
     auto frequentLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
         const auto& infos = history().mActiveLayerInfos;
         return std::count_if(infos.begin(), infos.end(), [now](const auto& pair) {
-            return pair.second.second->isFrequent(now);
+            return pair.second.second->isFrequent(now).isFrequent;
         });
     }
 
@@ -95,6 +95,13 @@
         });
     }
 
+    auto clearLayerHistoryCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
+        const auto& infos = history().mActiveLayerInfos;
+        return std::count_if(infos.begin(), infos.end(), [now](const auto& pair) {
+            return pair.second.second->isFrequent(now).clearHistory;
+        });
+    }
+
     void setDefaultLayerVote(Layer* layer,
                              LayerHistory::LayerVoteType vote) NO_THREAD_SAFETY_ANALYSIS {
         auto [found, layerPair] = history().findLayer(layer->getSequence());
@@ -764,6 +771,7 @@
     time += std::chrono::nanoseconds(3s).count();
     history().record(layer->getSequence(), layer->getLayerProps(), time, time,
                      LayerHistory::LayerUpdateType::Buffer);
+    EXPECT_EQ(0, clearLayerHistoryCount(time));
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summarizeLayerHistory(time)[0].vote);
     EXPECT_EQ(60_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
@@ -778,6 +786,7 @@
     time += (MAX_FREQUENT_LAYER_PERIOD_NS + 1ms).count();
     history().record(layer->getSequence(), layer->getLayerProps(), time, time,
                      LayerHistory::LayerUpdateType::Buffer);
+    EXPECT_EQ(0, clearLayerHistoryCount(time));
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
     EXPECT_EQ(1, activeLayerCount());
@@ -787,6 +796,7 @@
     // posting another buffer should keep the layer infrequent
     history().record(layer->getSequence(), layer->getLayerProps(), time, time,
                      LayerHistory::LayerUpdateType::Buffer);
+    EXPECT_EQ(0, clearLayerHistoryCount(time));
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
     EXPECT_EQ(1, activeLayerCount());
@@ -798,6 +808,7 @@
                      LayerHistory::LayerUpdateType::Buffer);
     history().record(layer->getSequence(), layer->getLayerProps(), time, time,
                      LayerHistory::LayerUpdateType::Buffer);
+    EXPECT_EQ(1, clearLayerHistoryCount(time));
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
     EXPECT_EQ(1, activeLayerCount());
@@ -808,6 +819,7 @@
     time += std::chrono::nanoseconds(3s).count();
     history().record(layer->getSequence(), layer->getLayerProps(), time, time,
                      LayerHistory::LayerUpdateType::Buffer);
+    EXPECT_EQ(0, clearLayerHistoryCount(time));
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
     EXPECT_EQ(1, activeLayerCount());
@@ -818,6 +830,64 @@
     time += (60_Hz).getPeriodNsecs();
     history().record(layer->getSequence(), layer->getLayerProps(), time, time,
                      LayerHistory::LayerUpdateType::Buffer);
+    EXPECT_EQ(0, clearLayerHistoryCount(time));
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, inconclusiveLayerBecomingFrequent) {
+    auto layer = createLayer();
+
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // Fill up the window with frequent updates
+    for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE; i++) {
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
+        time += (60_Hz).getPeriodNsecs();
+
+        EXPECT_EQ(1, layerCount());
+        ASSERT_EQ(1, summarizeLayerHistory(time).size());
+        EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+        EXPECT_EQ(1, activeLayerCount());
+        EXPECT_EQ(1, frequentLayerCount(time));
+    }
+
+    // posting infrequent buffers after long inactivity should make the layer
+    // inconclusive but frequent.
+    time += std::chrono::nanoseconds(3s).count();
+    history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                     LayerHistory::LayerUpdateType::Buffer);
+    time += (MAX_FREQUENT_LAYER_PERIOD_NS + 1ms).count();
+    history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                     LayerHistory::LayerUpdateType::Buffer);
+    EXPECT_EQ(0, clearLayerHistoryCount(time));
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // posting more buffers should make the layer frequent and switch the refresh rate to max
+    // by clearing the history
+    history().record(layer->getSequence(), layer->getLayerProps(), 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);
+    EXPECT_EQ(1, clearLayerHistoryCount(time));
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
     EXPECT_EQ(1, activeLayerCount());
diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
index 99c1d23..97ef5a2 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 \
@@ -47,13 +36,13 @@
 class ExpectLayerLifecycleListener : public LayerLifecycleManager::ILifecycleListener {
 public:
     void onLayerAdded(const RequestedLayerState& layer) override {
-        mActualLayersAdded.emplace(layer.id);
+        mActualLayersAdded.push_back(layer.id);
     };
     void onLayerDestroyed(const RequestedLayerState& layer) override {
         mActualLayersDestroyed.emplace(layer.id);
     };
 
-    void expectLayersAdded(const std::unordered_set<uint32_t>& expectedLayersAdded) {
+    void expectLayersAdded(const std::vector<uint32_t>& expectedLayersAdded) {
         EXPECT_EQ(expectedLayersAdded, mActualLayersAdded);
         mActualLayersAdded.clear();
     }
@@ -62,73 +51,28 @@
         mActualLayersDestroyed.clear();
     }
 
-    std::unordered_set<uint32_t> mActualLayersAdded;
+    std::vector<uint32_t> mActualLayersAdded;
     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();
@@ -383,7 +318,8 @@
 
     EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
     lifecycleManager.commitChanges();
-    auto bgLayerId = LayerCreationArgs::getInternalLayerId(1);
+    ASSERT_EQ(listener->mActualLayersAdded.size(), 2u);
+    auto bgLayerId = listener->mActualLayersAdded[1];
     listener->expectLayersAdded({1, bgLayerId});
     listener->expectLayersDestroyed({});
     EXPECT_EQ(getRequestedLayerState(lifecycleManager, bgLayerId)->color.a, 0.5_hf);
@@ -403,13 +339,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);
 
@@ -418,7 +353,8 @@
 
     EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
     lifecycleManager.commitChanges();
-    auto bgLayerId = LayerCreationArgs::getInternalLayerId(1);
+    ASSERT_EQ(listener->mActualLayersAdded.size(), 2u);
+    auto bgLayerId = listener->mActualLayersAdded[1];
     listener->expectLayersAdded({1, bgLayerId});
     listener->expectLayersDestroyed({bgLayerId});
 }
@@ -437,8 +373,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});
@@ -448,7 +383,8 @@
 
     EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
     lifecycleManager.commitChanges();
-    auto bgLayerId = LayerCreationArgs::getInternalLayerId(1);
+    ASSERT_EQ(listener->mActualLayersAdded.size(), 2u);
+    auto bgLayerId = listener->mActualLayersAdded[1];
     listener->expectLayersAdded({1, bgLayerId});
     listener->expectLayersDestroyed({1, bgLayerId});
 }
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index db0b907..b8a7446 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, ...)                                    \
@@ -81,6 +79,7 @@
                                         .displays = mFrontEndDisplayInfos,
                                         .displayChanges = hasDisplayChanges,
                                         .globalShadowSettings = globalShadowSettings,
+                                        .supportsBlur = true,
                                         .supportedLayerGenericMetadata = {},
                                         .genericLayerMetadataKeyMap = {}};
         actualBuilder.update(args);
@@ -268,7 +267,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 +296,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);
 
@@ -311,6 +309,44 @@
     EXPECT_EQ(getSnapshot(1)->frameRate.type, scheduler::LayerInfo::FrameRateCompatibility::NoVote);
 }
 
+TEST_F(LayerSnapshotTest, canCropTouchableRegion) {
+    // ROOT
+    // ├── 1
+    // │   ├── 11
+    // │   │   └── 111 (touchregion set to touch but cropped by layer 13)
+    // │   ├── 12
+    // │   │   ├── 121
+    // │   │   └── 122
+    // │   │       └── 1221
+    // │   └── 13 (crop set to touchCrop)
+    // └── 2
+
+    Rect touchCrop{300, 300, 400, 500};
+    setCrop(13, touchCrop);
+    Region touch{Rect{0, 0, 1000, 1000}};
+    setTouchableRegionCrop(111, touch, /*touchCropId=*/13, /*replaceTouchableRegionWithCrop=*/true);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 111})->inputInfo.touchableRegion.bounds(), touchCrop);
+
+    Rect modifiedTouchCrop{100, 300, 400, 700};
+    setCrop(13, modifiedTouchCrop);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 111})->inputInfo.touchableRegion.bounds(), modifiedTouchCrop);
+}
+
+TEST_F(LayerSnapshotTest, blurUpdatesWhenAlphaChanges) {
+    static constexpr int blurRadius = 42;
+    setBackgroundBlurRadius(1221, blurRadius);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1221})->backgroundBlurRadius, blurRadius);
+
+    static constexpr float alpha = 0.5;
+    setAlpha(12, alpha);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1221})->backgroundBlurRadius, blurRadius * alpha);
+}
+
 // Display Mirroring Tests
 // tree with 3 levels of children
 // ROOT (DISPLAY 0)
@@ -330,7 +366,7 @@
     createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
     setLayerStack(3, 1);
 
-    std::vector<uint32_t> expected = {3, 1, 11, 111, 13, 2, 1, 11, 111, 12, 121, 122, 1221, 13, 2};
+    std::vector<uint32_t> expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 3, 1, 11, 111, 13, 2};
     UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
 }
 
@@ -349,8 +385,8 @@
     createDisplayMirrorLayer(4, ui::LayerStack::fromValue(0));
     setLayerStack(4, 4);
 
-    std::vector<uint32_t> expected = {4,   1,  11, 111, 13, 2,   3,  1, 11,
-                                      111, 13, 2,  1,   11, 111, 13, 2};
+    std::vector<uint32_t> expected = {1,  11, 111, 13, 2,  3,   1,  11, 111,
+                                      13, 2,  4,   1,  11, 111, 13, 2};
     UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
     EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootId = 3})->outputFilter.layerStack.id, 3u);
     EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootId = 4})->outputFilter.layerStack.id, 4u);
@@ -373,7 +409,7 @@
     setCrop(111, Rect{200, 200});
     Region touch{Rect{0, 0, 1000, 1000}};
     setTouchableRegion(111, touch);
-    std::vector<uint32_t> expected = {3, 1, 11, 111, 13, 2, 1, 11, 111, 13, 2};
+    std::vector<uint32_t> expected = {1, 11, 111, 13, 2, 3, 1, 11, 111, 13, 2};
     UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
     EXPECT_TRUE(getSnapshot({.id = 111})->inputInfo.touchableRegion.hasSameRects(touch));
     Region touchCroppedByMirrorRoot{Rect{0, 0, 50, 50}};
@@ -385,7 +421,7 @@
     setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
     createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
     setLayerStack(3, 1);
-    std::vector<uint32_t> expected = {3, 1, 11, 111, 13, 2, 1, 11, 111, 12, 121, 122, 1221, 13, 2};
+    std::vector<uint32_t> expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 3, 1, 11, 111, 13, 2};
     UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
     destroyLayerHandle(3);
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
@@ -395,8 +431,8 @@
     size_t startingNumSnapshots = mSnapshotBuilder.getSnapshots().size();
     createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
     setLayerStack(3, 1);
-    std::vector<uint32_t> expected = {3, 1,  11,  111, 12,  121, 122,  1221, 13, 2,
-                                      1, 11, 111, 12,  121, 122, 1221, 13,   2};
+    std::vector<uint32_t> expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 3,
+                                      1, 11, 111, 12, 121, 122, 1221, 13, 2};
     UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
     destroyLayerHandle(3);
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
diff --git a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
index 2d66d3c..0d66d59 100644
--- a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
@@ -25,13 +25,15 @@
 #include <ui/DisplayId.h>
 #include <chrono>
 #include "TestableSurfaceFlinger.h"
-#include "mock/DisplayHardware/MockAidlPowerHalWrapper.h"
+#include "mock/DisplayHardware/MockIPowerHintSession.h"
+#include "mock/DisplayHardware/MockPowerHalController.h"
 
 using namespace android;
 using namespace android::Hwc2::mock;
 using namespace android::hardware::power;
 using namespace std::chrono_literals;
 using namespace testing;
+using namespace android::power;
 
 namespace android::Hwc2::impl {
 
@@ -42,28 +44,32 @@
     void fakeBasicFrameTiming(TimePoint startTime, Duration vsyncPeriod);
     void setExpectedTiming(Duration totalFrameTargetDuration, TimePoint expectedPresentTime);
     Duration getFenceWaitDelayDuration(bool skipValidate);
+    Duration getErrorMargin();
 
 protected:
     TestableSurfaceFlinger mFlinger;
     std::unique_ptr<PowerAdvisor> mPowerAdvisor;
-    NiceMock<MockAidlPowerHalWrapper>* mMockAidlWrapper;
-    Duration kErrorMargin = 1ms;
+    MockPowerHalController* mMockPowerHalController;
+    sp<MockIPowerHintSession> mMockPowerHintSession;
 };
 
-void PowerAdvisorTest::SetUp() FTL_FAKE_GUARD(mPowerAdvisor->mPowerHalMutex) {
-    std::unique_ptr<MockAidlPowerHalWrapper> mockAidlWrapper =
-            std::make_unique<NiceMock<MockAidlPowerHalWrapper>>();
-    mPowerAdvisor = std::make_unique<PowerAdvisor>(*mFlinger.flinger());
-    ON_CALL(*mockAidlWrapper.get(), supportsPowerHintSession()).WillByDefault(Return(true));
-    ON_CALL(*mockAidlWrapper.get(), startPowerHintSession()).WillByDefault(Return(true));
-    mPowerAdvisor->mHalWrapper = std::move(mockAidlWrapper);
-    mMockAidlWrapper =
-            reinterpret_cast<NiceMock<MockAidlPowerHalWrapper>*>(mPowerAdvisor->mHalWrapper.get());
+void PowerAdvisorTest::SetUp() {
+    mPowerAdvisor = std::make_unique<impl::PowerAdvisor>(*mFlinger.flinger());
+    mPowerAdvisor->mPowerHal = std::make_unique<NiceMock<MockPowerHalController>>();
+    mMockPowerHalController =
+            reinterpret_cast<MockPowerHalController*>(mPowerAdvisor->mPowerHal.get());
+    ON_CALL(*mMockPowerHalController, getHintSessionPreferredRate)
+            .WillByDefault(Return(HalResult<int64_t>::fromStatus(binder::Status::ok(), 16000)));
 }
 
 void PowerAdvisorTest::startPowerHintSession() {
     const std::vector<int32_t> threadIds = {1, 2, 3};
-    mPowerAdvisor->enablePowerHint(true);
+    mMockPowerHintSession = android::sp<NiceMock<MockIPowerHintSession>>::make();
+    ON_CALL(*mMockPowerHalController, createHintSession)
+            .WillByDefault(
+                    Return(HalResult<sp<IPowerHintSession>>::fromStatus(binder::Status::ok(),
+                                                                        mMockPowerHintSession)));
+    mPowerAdvisor->enablePowerHintSession(true);
     mPowerAdvisor->startPowerHintSession(threadIds);
 }
 
@@ -76,7 +82,7 @@
 void PowerAdvisorTest::fakeBasicFrameTiming(TimePoint startTime, Duration vsyncPeriod) {
     mPowerAdvisor->setCommitStart(startTime);
     mPowerAdvisor->setFrameDelay(0ns);
-    mPowerAdvisor->setTargetWorkDuration(vsyncPeriod);
+    mPowerAdvisor->updateTargetWorkDuration(vsyncPeriod);
 }
 
 Duration PowerAdvisorTest::getFenceWaitDelayDuration(bool skipValidate) {
@@ -84,6 +90,10 @@
                          : PowerAdvisor::kFenceWaitStartDelayValidated);
 }
 
+Duration PowerAdvisorTest::getErrorMargin() {
+    return mPowerAdvisor->sTargetSafetyMargin;
+}
+
 namespace {
 
 TEST_F(PowerAdvisorTest, hintSessionUseHwcDisplay) {
@@ -109,8 +119,11 @@
     // increment the frame
     startTime += vsyncPeriod;
 
-    const Duration expectedDuration = kErrorMargin + presentDuration + postCompDuration;
-    EXPECT_CALL(*mMockAidlWrapper, sendActualWorkDuration(Eq(expectedDuration), _)).Times(1);
+    const Duration expectedDuration = getErrorMargin() + presentDuration + postCompDuration;
+    EXPECT_CALL(*mMockPowerHintSession,
+                reportActualWorkDuration(ElementsAre(
+                        Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns())))))
+            .Times(1);
 
     fakeBasicFrameTiming(startTime, vsyncPeriod);
     setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
@@ -118,7 +131,7 @@
     mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime + 1ms, startTime + 1500us);
     mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime + 2ms, startTime + 2500us);
     mPowerAdvisor->setSfPresentTiming(startTime, startTime + presentDuration);
-    mPowerAdvisor->sendActualWorkDuration();
+    mPowerAdvisor->reportActualWorkDuration();
 }
 
 TEST_F(PowerAdvisorTest, hintSessionSubtractsHwcFenceTime) {
@@ -145,9 +158,12 @@
     // increment the frame
     startTime += vsyncPeriod;
 
-    const Duration expectedDuration = kErrorMargin + presentDuration +
+    const Duration expectedDuration = getErrorMargin() + presentDuration +
             getFenceWaitDelayDuration(false) - hwcBlockedDuration + postCompDuration;
-    EXPECT_CALL(*mMockAidlWrapper, sendActualWorkDuration(Eq(expectedDuration), _)).Times(1);
+    EXPECT_CALL(*mMockPowerHintSession,
+                reportActualWorkDuration(ElementsAre(
+                        Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns())))))
+            .Times(1);
 
     fakeBasicFrameTiming(startTime, vsyncPeriod);
     setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
@@ -157,7 +173,7 @@
     // now report the fence as having fired during the display HWC time
     mPowerAdvisor->setSfPresentTiming(startTime + 2ms + hwcBlockedDuration,
                                       startTime + presentDuration);
-    mPowerAdvisor->sendActualWorkDuration();
+    mPowerAdvisor->reportActualWorkDuration();
 }
 
 TEST_F(PowerAdvisorTest, hintSessionUsingSecondaryVirtualDisplays) {
@@ -185,8 +201,11 @@
     // increment the frame
     startTime += vsyncPeriod;
 
-    const Duration expectedDuration = kErrorMargin + presentDuration + postCompDuration;
-    EXPECT_CALL(*mMockAidlWrapper, sendActualWorkDuration(Eq(expectedDuration), _)).Times(1);
+    const Duration expectedDuration = getErrorMargin() + presentDuration + postCompDuration;
+    EXPECT_CALL(*mMockPowerHintSession,
+                reportActualWorkDuration(ElementsAre(
+                        Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns())))))
+            .Times(1);
 
     fakeBasicFrameTiming(startTime, vsyncPeriod);
     setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
@@ -196,7 +215,7 @@
     mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime + 1ms, startTime + 1500us);
     mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime + 2ms, startTime + 2500us);
     mPowerAdvisor->setSfPresentTiming(startTime, startTime + presentDuration);
-    mPowerAdvisor->sendActualWorkDuration();
+    mPowerAdvisor->reportActualWorkDuration();
 }
 
 } // namespace
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index f4d052d..d63e187 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) {
@@ -3002,5 +3026,21 @@
     EXPECT_FRAME_RATE_MODE(kMode60, 30_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
 }
 
+TEST_P(RefreshRateSelectorTest, frameRateNotInRange) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
+
+    constexpr FpsRanges k60Only = {{60_Hz, 90_Hz}, {60_Hz, 60_Hz}};
+    constexpr FpsRanges kAll = {{0_Hz, 90_Hz}, {0_Hz, 90_Hz}};
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({DisplayModeId(kModeId60), k60Only, kAll}));
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    layers[0].name = "Test layer";
+    layers[0].vote = LayerVoteType::Heuristic;
+    layers[0].desiredRefreshRate = 45_Hz;
+    EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+}
+
 } // namespace
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index dc76b4c..965e378 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -359,7 +359,8 @@
         EXPECT_EQ(expectedChoices, actualChoices);
     }
     {
-        // This display does not support 120 Hz, so we should choose 60 Hz despite the touch signal.
+        // The kDisplayId3 does not support 120Hz, The pacesetter display rate is chosen to be 120
+        // Hz. In this case only the display kDisplayId3 choose 60Hz as it does not support 120Hz.
         mScheduler
                 ->registerDisplay(kDisplayId3,
                                   std::make_shared<RefreshRateSelector>(kDisplay3Modes,
@@ -371,6 +372,26 @@
 
         expectedChoices = ftl::init::map<
                 const PhysicalDisplayId&,
+                DisplayModeChoice>(kDisplayId1, FrameRateMode{120_Hz, kDisplay1Mode120},
+                                   globalSignals)(kDisplayId2,
+                                                  FrameRateMode{120_Hz, kDisplay2Mode120},
+                                                  globalSignals)(kDisplayId3,
+                                                                 FrameRateMode{60_Hz,
+                                                                               kDisplay3Mode60},
+                                                                 globalSignals);
+
+        const auto actualChoices = mScheduler->chooseDisplayModes();
+        EXPECT_EQ(expectedChoices, actualChoices);
+    }
+    {
+        // We should choose 60Hz despite the touch signal as pacesetter only supports 60Hz
+        mScheduler->setPacesetterDisplay(kDisplayId3);
+        const GlobalSignals globalSignals = {.touch = true};
+        mScheduler->replaceTouchTimer(10);
+        mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
+
+        expectedChoices = ftl::init::map<
+                const PhysicalDisplayId&,
                 DisplayModeChoice>(kDisplayId1, FrameRateMode{60_Hz, kDisplay1Mode60},
                                    globalSignals)(kDisplayId2,
                                                   FrameRateMode{60_Hz, kDisplay2Mode60},
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
index 6a9c970..1cc9ba4 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
@@ -17,12 +17,60 @@
 #undef LOG_TAG
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
+#include <scheduler/Fps.h>
+
 #include "DisplayTransactionTestHelpers.h"
+#include "FpsOps.h"
 
 namespace android {
 namespace {
 
-class CreateDisplayTest : public DisplayTransactionTest {};
+class CreateDisplayTest : public DisplayTransactionTest {
+public:
+    void createDisplayWithRequestedRefreshRate(const String8& name, uint64_t displayId,
+                                               float pacesetterDisplayRefreshRate,
+                                               float requestedRefreshRate,
+                                               float expectedAdjustedRefreshRate) {
+        // --------------------------------------------------------------------
+        // Call Expectations
+
+        // --------------------------------------------------------------------
+        // Invocation
+
+        sp<IBinder> displayToken = mFlinger.createDisplay(name, false, requestedRefreshRate);
+
+        // --------------------------------------------------------------------
+        // Postconditions
+
+        // The display should have been added to the current state
+        ASSERT_TRUE(hasCurrentDisplayState(displayToken));
+        const auto& display = getCurrentDisplayState(displayToken);
+        EXPECT_TRUE(display.isVirtual());
+        EXPECT_EQ(display.requestedRefreshRate, Fps::fromValue(requestedRefreshRate));
+        EXPECT_EQ(name.string(), display.displayName);
+
+        std::optional<VirtualDisplayId> vid =
+                DisplayId::fromValue<VirtualDisplayId>(displayId | DisplayId::FLAG_VIRTUAL);
+        ASSERT_TRUE(vid.has_value());
+
+        sp<DisplayDevice> device =
+                mFlinger.createVirtualDisplayDevice(displayToken, *vid, requestedRefreshRate);
+        EXPECT_TRUE(device->isVirtual());
+        device->adjustRefreshRate(Fps::fromValue(pacesetterDisplayRefreshRate));
+        // verifying desired value
+        EXPECT_EQ(device->getAdjustedRefreshRate(), Fps::fromValue(expectedAdjustedRefreshRate));
+        // verifying rounding up
+        if (requestedRefreshRate < pacesetterDisplayRefreshRate) {
+            EXPECT_GE(device->getAdjustedRefreshRate(), Fps::fromValue(requestedRefreshRate));
+        } else {
+            EXPECT_EQ(device->getAdjustedRefreshRate(),
+                      Fps::fromValue(pacesetterDisplayRefreshRate));
+        }
+
+        // --------------------------------------------------------------------
+        // Cleanup conditions
+    }
+};
 
 TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForNonsecureDisplay) {
     const String8 name("virtual.test");
@@ -84,5 +132,82 @@
     EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 }
 
+// Requesting 0 tells SF not to do anything, i.e., default to refresh as physical displays
+TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRate0) {
+    const String8 displayName("virtual.test");
+    const uint64_t displayId = 123ull;
+    const float kPacesetterDisplayRefreshRate = 60.f;
+    const float kRequestedRefreshRate = 0.f;
+    const float kExpectedAdjustedRefreshRate = 0.f;
+    createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate,
+                                          kRequestedRefreshRate, kExpectedAdjustedRefreshRate);
+}
+
+// Requesting negative refresh rate, will be ignored, same as requesting 0
+TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateNegative) {
+    const String8 displayName("virtual.test");
+    const uint64_t displayId = 123ull;
+    const float kPacesetterDisplayRefreshRate = 60.f;
+    const float kRequestedRefreshRate = -60.f;
+    const float kExpectedAdjustedRefreshRate = 0.f;
+    createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate,
+                                          kRequestedRefreshRate, kExpectedAdjustedRefreshRate);
+}
+
+// Requesting a higher refresh rate than the pacesetter
+TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateHigh) {
+    const String8 displayName("virtual.test");
+    const uint64_t displayId = 123ull;
+    const float kPacesetterDisplayRefreshRate = 60.f;
+    const float kRequestedRefreshRate = 90.f;
+    const float kExpectedAdjustedRefreshRate = 60.f;
+    createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate,
+                                          kRequestedRefreshRate, kExpectedAdjustedRefreshRate);
+}
+
+// Requesting the same refresh rate as the pacesetter
+TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateSame) {
+    const String8 displayName("virtual.test");
+    const uint64_t displayId = 123ull;
+    const float kPacesetterDisplayRefreshRate = 60.f;
+    const float kRequestedRefreshRate = 60.f;
+    const float kExpectedAdjustedRefreshRate = 60.f;
+    createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate,
+                                          kRequestedRefreshRate, kExpectedAdjustedRefreshRate);
+}
+
+// Requesting a divisor (30) of the pacesetter (60) should be honored
+TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateDivisor) {
+    const String8 displayName("virtual.test");
+    const uint64_t displayId = 123ull;
+    const float kPacesetterDisplayRefreshRate = 60.f;
+    const float kRequestedRefreshRate = 30.f;
+    const float kExpectedAdjustedRefreshRate = 30.f;
+    createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate,
+                                          kRequestedRefreshRate, kExpectedAdjustedRefreshRate);
+}
+
+// Requesting a non divisor (45) of the pacesetter (120) should round up to a divisor (60)
+TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateNoneDivisor) {
+    const String8 displayName("virtual.test");
+    const uint64_t displayId = 123ull;
+    const float kPacesetterDisplayRefreshRate = 120.f;
+    const float kRequestedRefreshRate = 45.f;
+    const float kExpectedAdjustedRefreshRate = 60.f;
+    createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate,
+                                          kRequestedRefreshRate, kExpectedAdjustedRefreshRate);
+}
+
+// Requesting a non divisor (75) of the pacesetter (120) should round up to pacesetter (120)
+TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateNoneDivisorMax) {
+    const String8 displayName("virtual.test");
+    const uint64_t displayId = 123ull;
+    const float kPacesetterDisplayRefreshRate = 120.f;
+    const float kRequestedRefreshRate = 75.f;
+    const float kExpectedAdjustedRefreshRate = 120.f;
+    createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate,
+                                          kRequestedRefreshRate, kExpectedAdjustedRefreshRate);
+}
+
 } // namespace
 } // namespace android
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_GetDisplayStatsTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp
new file mode 100644
index 0000000..29acfaa
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp
@@ -0,0 +1,116 @@
+/*
+ * 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 "SurfaceFlingerGetDisplayStatsTest"
+
+#include <compositionengine/Display.h>
+#include <compositionengine/mock/DisplaySurface.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <renderengine/mock/RenderEngine.h>
+#include <ui/DisplayStatInfo.h>
+#include "TestableSurfaceFlinger.h"
+#include "mock/DisplayHardware/MockComposer.h"
+#include "mock/DisplayHardware/MockPowerAdvisor.h"
+#include "mock/MockTimeStats.h"
+#include "mock/system/window/MockNativeWindow.h"
+
+using namespace android;
+using namespace testing;
+
+namespace android {
+namespace {
+using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
+
+constexpr hal::HWDisplayId HWC_DISPLAY = FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID;
+constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u);
+constexpr int DEFAULT_DISPLAY_WIDTH = 1920;
+constexpr int DEFAULT_DISPLAY_HEIGHT = 1024;
+
+class SurfaceFlingerGetDisplayStatsTest : public Test {
+public:
+    void SetUp() override;
+
+protected:
+    TestableSurfaceFlinger mFlinger;
+    renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
+    sp<DisplayDevice> mDisplay;
+    sp<compositionengine::mock::DisplaySurface> mDisplaySurface =
+            sp<compositionengine::mock::DisplaySurface>::make();
+    sp<mock::NativeWindow> mNativeWindow = sp<mock::NativeWindow>::make();
+    mock::TimeStats* mTimeStats = new mock::TimeStats();
+    Hwc2::mock::PowerAdvisor* mPowerAdvisor = nullptr;
+    Hwc2::mock::Composer* mComposer = nullptr;
+};
+
+void SurfaceFlingerGetDisplayStatsTest::SetUp() {
+    mFlinger.setupMockScheduler({.displayId = DEFAULT_DISPLAY_ID});
+    mComposer = new Hwc2::mock::Composer();
+    mPowerAdvisor = new Hwc2::mock::PowerAdvisor();
+    mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
+    mFlinger.setupTimeStats(std::shared_ptr<TimeStats>(mTimeStats));
+    mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer));
+    mFlinger.setupPowerAdvisor(std::unique_ptr<Hwc2::PowerAdvisor>(mPowerAdvisor));
+    static constexpr bool kIsPrimary = true;
+    FakeHwcDisplayInjector(DEFAULT_DISPLAY_ID, hal::DisplayType::PHYSICAL, kIsPrimary)
+            .setPowerMode(hal::PowerMode::ON)
+            .inject(&mFlinger, mComposer);
+    auto compostionEngineDisplayArgs =
+            compositionengine::DisplayCreationArgsBuilder()
+                    .setId(DEFAULT_DISPLAY_ID)
+                    .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT})
+                    .setPowerAdvisor(mPowerAdvisor)
+                    .setName("injected display")
+                    .build();
+    auto compositionDisplay =
+            compositionengine::impl::createDisplay(mFlinger.getCompositionEngine(),
+                                                   std::move(compostionEngineDisplayArgs));
+    mDisplay =
+            FakeDisplayDeviceInjector(mFlinger, compositionDisplay,
+                                      ui::DisplayConnectionType::Internal, HWC_DISPLAY, kIsPrimary)
+                    .setDisplaySurface(mDisplaySurface)
+                    .setNativeWindow(mNativeWindow)
+                    .setPowerMode(hal::PowerMode::ON)
+                    .setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector())
+                    .skipRegisterDisplay()
+                    .inject();
+}
+
+// TODO (b/277364366): Clients should be updated to pass in the display they want.
+TEST_F(SurfaceFlingerGetDisplayStatsTest, nullptrSucceeds) {
+    DisplayStatInfo info;
+    status_t status = mFlinger.getDisplayStats(nullptr, &info);
+    EXPECT_EQ(status, NO_ERROR);
+}
+
+TEST_F(SurfaceFlingerGetDisplayStatsTest, explicitToken) {
+    DisplayStatInfo info;
+    status_t status = mFlinger.getDisplayStats(mDisplay->getDisplayToken().promote(), &info);
+    EXPECT_EQ(status, NO_ERROR);
+}
+
+TEST_F(SurfaceFlingerGetDisplayStatsTest, invalidToken) {
+    const String8 displayName("fakeDisplay");
+    sp<IBinder> displayToken = mFlinger.createDisplay(displayName, false);
+    DisplayStatInfo info;
+    status_t status = mFlinger.getDisplayStats(displayToken, &info);
+    EXPECT_EQ(status, NAME_NOT_FOUND);
+}
+
+} // namespace
+} // 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_PowerHintTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
index 7839ef0..d0290ea 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
@@ -70,7 +70,6 @@
     mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
     mFlinger.setupTimeStats(std::shared_ptr<TimeStats>(mTimeStats));
     mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer));
-    mFlinger.setPowerHintSessionMode(true, true);
     mFlinger.setupPowerAdvisor(std::unique_ptr<Hwc2::PowerAdvisor>(mPowerAdvisor));
     static constexpr bool kIsPrimary = true;
     FakeHwcDisplayInjector(DEFAULT_DISPLAY_ID, hal::DisplayType::PHYSICAL, kIsPrimary)
@@ -100,7 +99,7 @@
 TEST_F(SurfaceFlingerPowerHintTest, sendDurationsIncludingHwcWaitTime) {
     ON_CALL(*mPowerAdvisor, usePowerHintSession()).WillByDefault(Return(true));
 
-    EXPECT_CALL(*mPowerAdvisor, setTargetWorkDuration(_)).Times(1);
+    EXPECT_CALL(*mPowerAdvisor, updateTargetWorkDuration(_)).Times(1);
     EXPECT_CALL(*mDisplaySurface,
                 prepareFrame(compositionengine::DisplaySurface::CompositionType::Hwc))
             .Times(1);
@@ -109,7 +108,7 @@
         std::this_thread::sleep_for(kMockHwcRunTime);
         return hardware::graphics::composer::V2_1::Error::NONE;
     });
-    EXPECT_CALL(*mPowerAdvisor, sendActualWorkDuration()).Times(1);
+    EXPECT_CALL(*mPowerAdvisor, reportActualWorkDuration()).Times(1);
 
     const TimePoint frameTime = scheduler::SchedulerClock::now();
     constexpr Period kMockVsyncPeriod = 15ms;
@@ -121,7 +120,7 @@
 
     mDisplay->setPowerMode(hal::PowerMode::DOZE);
 
-    EXPECT_CALL(*mPowerAdvisor, setTargetWorkDuration(_)).Times(0);
+    EXPECT_CALL(*mPowerAdvisor, updateTargetWorkDuration(_)).Times(0);
     EXPECT_CALL(*mDisplaySurface,
                 prepareFrame(compositionengine::DisplaySurface::CompositionType::Hwc))
             .Times(1);
@@ -130,7 +129,7 @@
         std::this_thread::sleep_for(kMockHwcRunTime);
         return hardware::graphics::composer::V2_1::Error::NONE;
     });
-    EXPECT_CALL(*mPowerAdvisor, sendActualWorkDuration()).Times(0);
+    EXPECT_CALL(*mPowerAdvisor, reportActualWorkDuration()).Times(0);
 
     const TimePoint frameTime = scheduler::SchedulerClock::now();
     constexpr Period kMockVsyncPeriod = 15ms;
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index d4b4434..3b6a987 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -65,10 +65,6 @@
 
     auto refreshRateSelector() { return pacesetterSelectorPtr(); }
 
-    const auto& refreshRateSelectors() const NO_THREAD_SAFETY_ANALYSIS {
-        return mRefreshRateSelectors;
-    }
-
     void registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
         registerDisplay(displayId, std::move(selectorPtr),
                         std::make_unique<mock::VsyncController>(),
@@ -102,7 +98,6 @@
     }
 
     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..945e488 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -48,12 +48,17 @@
 #include "TestableScheduler.h"
 #include "mock/DisplayHardware/MockComposer.h"
 #include "mock/DisplayHardware/MockDisplayMode.h"
+#include "mock/DisplayHardware/MockPowerAdvisor.h"
 #include "mock/MockEventThread.h"
 #include "mock/MockFrameTimeline.h"
 #include "mock/MockFrameTracer.h"
 #include "mock/MockSchedulerCallback.h"
+#include "mock/system/window/MockNativeWindow.h"
 
 namespace android {
+
+struct DisplayStatInfo;
+
 namespace renderengine {
 
 class RenderEngine;
@@ -344,10 +349,6 @@
         layer->mDrawingParent = drawingParent;
     }
 
-    void setPowerHintSessionMode(bool early, bool late) {
-        mFlinger->mPowerHintSessionMode = {.late = late, .early = early};
-    }
-
     /* ------------------------------------------------------------------------
      * Forwarding for functions being tested
      */
@@ -379,8 +380,8 @@
 
     void commitAndComposite() { mFlinger->composite(commit(), kVsyncId); }
 
-    auto createDisplay(const String8& displayName, bool secure) {
-        return mFlinger->createDisplay(displayName, secure);
+    auto createDisplay(const String8& displayName, bool secure, float requestedRefreshRate = 0.0f) {
+        return mFlinger->createDisplay(displayName, secure, requestedRefreshRate);
     }
 
     auto destroyDisplay(const sp<IBinder>& displayToken) {
@@ -419,10 +420,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); }
 
@@ -449,8 +447,10 @@
     }
 
     auto traverseLayersInLayerStack(ui::LayerStack layerStack, int32_t uid,
+                                    std::unordered_set<uint32_t> excludeLayerIds,
                                     const LayerVector::Visitor& visitor) {
-        return mFlinger->SurfaceFlinger::traverseLayersInLayerStack(layerStack, uid, visitor);
+        return mFlinger->SurfaceFlinger::traverseLayersInLayerStack(layerStack, uid,
+                                                                    excludeLayerIds, visitor);
     }
 
     auto getDisplayNativePrimaries(const sp<IBinder>& displayToken,
@@ -466,19 +466,18 @@
         return mFlinger->mTransactionHandler.mPendingTransactionCount.load();
     }
 
-    auto setTransactionState(const FrameTimelineInfo& frameTimelineInfo,
-                             Vector<ComposerState>& states, const Vector<DisplayState>& displays,
-                             uint32_t flags, const sp<IBinder>& applyToken,
-                             const InputWindowCommands& inputWindowCommands,
-                             int64_t desiredPresentTime, bool isAutoTimestamp,
-                             const std::vector<client_cache_t>& uncacheBuffers,
-                             bool hasListenerCallbacks,
-                             std::vector<ListenerCallbacks>& listenerCallbacks,
-                             uint64_t transactionId) {
+    auto setTransactionState(
+            const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& states,
+            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
+            bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffers,
+            bool hasListenerCallbacks, std::vector<ListenerCallbacks>& listenerCallbacks,
+            uint64_t transactionId, const std::vector<uint64_t>& mergedTransactionIds) {
         return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken,
                                              inputWindowCommands, desiredPresentTime,
                                              isAutoTimestamp, uncacheBuffers, hasListenerCallbacks,
-                                             listenerCallbacks, transactionId);
+                                             listenerCallbacks, transactionId,
+                                             mergedTransactionIds);
     }
 
     auto setTransactionStateInternal(TransactionState& transaction) {
@@ -505,10 +504,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,
@@ -529,6 +529,28 @@
         mFlinger->getDynamicDisplayInfoFromToken(displayToken, dynamicDisplayInfo);
     }
 
+    sp<DisplayDevice> createVirtualDisplayDevice(const sp<IBinder> displayToken,
+                                                 VirtualDisplayId displayId,
+                                                 float requestedRefreshRate) {
+        constexpr ui::Size kResolution = {1080, 1920};
+        auto compositionDisplay = compositionengine::impl::
+                createDisplay(mFlinger->getCompositionEngine(),
+                              compositionengine::DisplayCreationArgsBuilder()
+                                      .setId(displayId)
+                                      .setPixels(kResolution)
+                                      .setPowerAdvisor(&mPowerAdvisor)
+                                      .build());
+        DisplayDeviceCreationArgs creationArgs(mFlinger, mFlinger->getHwComposer(), displayToken,
+                                               compositionDisplay);
+        creationArgs.requestedRefreshRate = Fps::fromValue(requestedRefreshRate);
+        creationArgs.nativeWindow = sp<mock::NativeWindow>::make();
+        return sp<DisplayDevice>::make(creationArgs);
+    }
+
+    status_t getDisplayStats(const sp<IBinder>& displayToken, DisplayStatInfo* outInfo) {
+        return mFlinger->getDisplayStats(displayToken, outInfo);
+    }
+
     /* ------------------------------------------------------------------------
      * Read-only access to private data to assert post-conditions.
      */
@@ -565,6 +587,7 @@
     auto& mutablePhysicalDisplays() { return mFlinger->mPhysicalDisplays; }
     auto& mutableDrawingState() { return mFlinger->mDrawingState; }
     auto& mutableGeometryDirty() { return mFlinger->mGeometryDirty; }
+    auto& mutableVisibleRegionsDirty() { return mFlinger->mVisibleRegionsDirty; }
     auto& mutableMainThreadId() { return mFlinger->mMainThreadId; }
     auto& mutablePendingHotplugEvents() { return mFlinger->mPendingHotplugEvents; }
     auto& mutableTexturePool() { return mFlinger->mTexturePool; }
@@ -576,6 +599,11 @@
     auto& mutableHwcPhysicalDisplayIdMap() { return getHwComposer().mPhysicalDisplayIdMap; }
     auto& mutablePrimaryHwcDisplayId() { return getHwComposer().mPrimaryHwcDisplayId; }
     auto& mutableActiveDisplayId() { return mFlinger->mActiveDisplayId; }
+    auto& mutablePreviouslyComposedLayers() { return mFlinger->mPreviouslyComposedLayers; }
+
+    auto& mutableActiveDisplayRotationFlags() {
+        return SurfaceFlinger::sActiveDisplayRotationFlags;
+    }
 
     auto fromHandle(const sp<IBinder>& handle) { return LayerHandle::getLayer(handle); }
 
@@ -966,6 +994,7 @@
     scheduler::mock::NoOpSchedulerCallback mNoOpSchedulerCallback;
     std::unique_ptr<frametimeline::impl::TokenManager> mTokenManager;
     scheduler::TestableScheduler* mScheduler = nullptr;
+    Hwc2::mock::PowerAdvisor mPowerAdvisor;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index c78148f..afb8efb 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -73,6 +73,7 @@
         FrameTimelineInfo frameTimelineInfo;
         std::vector<client_cache_t> uncacheBuffers;
         uint64_t id = static_cast<uint64_t>(-1);
+        std::vector<uint64_t> mergedTransactionIds;
         static_assert(0xffffffffffffffff == static_cast<uint64_t>(-1));
     };
 
@@ -108,7 +109,7 @@
                                      transaction.applyToken, transaction.inputWindowCommands,
                                      transaction.desiredPresentTime, transaction.isAutoTimestamp,
                                      transaction.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
-                                     transaction.id);
+                                     transaction.id, transaction.mergedTransactionIds);
 
         // If transaction is synchronous, SF applyTransactionState should time out (5s) wating for
         // SF to commit the transaction. If this is animation, it should not time out waiting.
@@ -135,7 +136,7 @@
                                      transaction.applyToken, transaction.inputWindowCommands,
                                      transaction.desiredPresentTime, transaction.isAutoTimestamp,
                                      transaction.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
-                                     transaction.id);
+                                     transaction.id, transaction.mergedTransactionIds);
 
         nsecs_t returnedTime = systemTime();
         EXPECT_LE(returnedTime, applicationSentTime + TRANSACTION_TIMEOUT);
@@ -166,7 +167,7 @@
                                      transactionA.applyToken, transactionA.inputWindowCommands,
                                      transactionA.desiredPresentTime, transactionA.isAutoTimestamp,
                                      transactionA.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
-                                     transactionA.id);
+                                     transactionA.id, transactionA.mergedTransactionIds);
 
         // This thread should not have been blocked by the above transaction
         // (5s is the timeout period that applyTransactionState waits for SF to
@@ -181,7 +182,7 @@
                                      transactionB.applyToken, transactionB.inputWindowCommands,
                                      transactionB.desiredPresentTime, transactionB.isAutoTimestamp,
                                      transactionB.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
-                                     transactionB.id);
+                                     transactionB.id, transactionB.mergedTransactionIds);
 
         // this thread should have been blocked by the above transaction
         // if this is an animation, this thread should be blocked for 5s
@@ -199,7 +200,7 @@
 
     void modulateVsync() {
         static_cast<void>(
-                mFlinger.mutableScheduler().mutableVsyncModulator().onRefreshRateChangeInitiated());
+                mFlinger.mutableScheduler().vsyncModulator().onRefreshRateChangeInitiated());
     }
 
     bool mHasListenerCallbacks = false;
@@ -218,7 +219,8 @@
                                  transactionA.displays, transactionA.flags, transactionA.applyToken,
                                  transactionA.inputWindowCommands, transactionA.desiredPresentTime,
                                  transactionA.isAutoTimestamp, transactionA.uncacheBuffers,
-                                 mHasListenerCallbacks, mCallbacks, transactionA.id);
+                                 mHasListenerCallbacks, mCallbacks, transactionA.id,
+                                 transactionA.mergedTransactionIds);
 
     auto& transactionQueue = mFlinger.getTransactionQueue();
     ASSERT_FALSE(transactionQueue.isEmpty());
@@ -238,7 +240,8 @@
                                  transactionA.displays, transactionA.flags, transactionA.applyToken,
                                  transactionA.inputWindowCommands, transactionA.desiredPresentTime,
                                  transactionA.isAutoTimestamp, transactionA.uncacheBuffers,
-                                 mHasListenerCallbacks, mCallbacks, transactionA.id);
+                                 mHasListenerCallbacks, mCallbacks, transactionA.id,
+                                 transactionA.mergedTransactionIds);
 
     auto& transactionQueue = mFlinger.getTransactionQueue();
     ASSERT_FALSE(transactionQueue.isEmpty());
@@ -251,7 +254,8 @@
     mFlinger.setTransactionState(empty.frameTimelineInfo, empty.states, empty.displays, empty.flags,
                                  empty.applyToken, empty.inputWindowCommands,
                                  empty.desiredPresentTime, empty.isAutoTimestamp,
-                                 empty.uncacheBuffers, mHasListenerCallbacks, mCallbacks, empty.id);
+                                 empty.uncacheBuffers, mHasListenerCallbacks, mCallbacks, empty.id,
+                                 empty.mergedTransactionIds);
 
     // flush transaction queue should flush as desiredPresentTime has
     // passed
@@ -274,6 +278,34 @@
     EXPECT_EQ(nullptr, ret.get());
 }
 
+class FakeExternalTexture : public renderengine::ExternalTexture {
+    const sp<GraphicBuffer> mEmptyBuffer = nullptr;
+    uint32_t mWidth;
+    uint32_t mHeight;
+    uint64_t mId;
+    PixelFormat mPixelFormat;
+    uint64_t mUsage;
+
+public:
+    FakeExternalTexture(BufferData& bufferData)
+          : mWidth(bufferData.getWidth()),
+            mHeight(bufferData.getHeight()),
+            mId(bufferData.getId()),
+            mPixelFormat(bufferData.getPixelFormat()),
+            mUsage(bufferData.getUsage()) {}
+    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; }
+    void remapBuffer() override {}
+    ~FakeExternalTexture() = default;
+};
+
 class LatchUnsignaledTest : public TransactionApplicationTest {
 public:
     void TearDown() override {
@@ -294,7 +326,8 @@
         return fence;
     }
 
-    ComposerState createComposerState(int layerId, sp<Fence> fence, uint64_t what) {
+    ComposerState createComposerState(int layerId, sp<Fence> fence, uint64_t what,
+                                      std::optional<sp<IBinder>> layerHandle = std::nullopt) {
         ComposerState state;
         state.state.bufferData =
                 std::make_shared<fake::BufferData>(/* bufferId */ 123L, /* width */ 1,
@@ -302,15 +335,20 @@
                                                    /* outUsage */ 0);
         state.state.bufferData->acquireFence = std::move(fence);
         state.state.layerId = layerId;
-        state.state.surface =
+        state.state.surface = layerHandle.value_or(
                 sp<Layer>::make(LayerCreationArgs(mFlinger.flinger(), nullptr, "TestLayer", 0, {}))
-                        ->getHandle();
+                        ->getHandle());
         state.state.bufferData->flags = BufferData::BufferDataChange::fenceChanged;
 
         state.state.what = what;
         if (what & layer_state_t::eCropChanged) {
             state.state.crop = Rect(1, 2, 3, 4);
         }
+        if (what & layer_state_t::eFlagsChanged) {
+            state.state.flags = layer_state_t::eEnableBackpressure;
+            state.state.mask = layer_state_t::eEnableBackpressure;
+        }
+
         return state;
     }
 
@@ -340,7 +378,11 @@
             std::vector<ResolvedComposerState> resolvedStates;
             resolvedStates.reserve(transaction.states.size());
             for (auto& state : transaction.states) {
-                resolvedStates.emplace_back(std::move(state));
+                ResolvedComposerState resolvedState;
+                resolvedState.state = std::move(state.state);
+                resolvedState.externalTexture =
+                        std::make_shared<FakeExternalTexture>(*resolvedState.state.bufferData);
+                resolvedStates.emplace_back(resolvedState);
             }
 
             TransactionState transactionState(transaction.frameTimelineInfo, resolvedStates,
@@ -348,9 +390,10 @@
                                               transaction.applyToken,
                                               transaction.inputWindowCommands,
                                               transaction.desiredPresentTime,
-                                              transaction.isAutoTimestamp, {}, systemTime(), 0,
+                                              transaction.isAutoTimestamp, {}, systemTime(),
                                               mHasListenerCallbacks, mCallbacks, getpid(),
-                                              static_cast<int>(getuid()), transaction.id);
+                                              static_cast<int>(getuid()), transaction.id,
+                                              transaction.mergedTransactionIds);
             mFlinger.setTransactionStateInternal(transactionState);
         }
         mFlinger.flushTransactionQueues();
@@ -601,6 +644,41 @@
     setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending);
 }
 
+TEST_F(LatchUnsignaledAutoSingleLayerTest, UnsignaledNotAppliedWhenThereAreSignaled_SignaledFirst) {
+    const sp<IBinder> kApplyToken1 =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const sp<IBinder> kApplyToken2 = sp<BBinder>::make();
+    const sp<IBinder> kApplyToken3 = sp<BBinder>::make();
+    const auto kLayerId1 = 1;
+    const auto kLayerId2 = 2;
+    const auto kExpectedTransactionsPending = 1u;
+
+    const auto signaledTransaction =
+            createTransactionInfo(kApplyToken1,
+                                  {
+                                          createComposerState(kLayerId1,
+                                                              fence(Fence::Status::Signaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    const auto signaledTransaction2 =
+            createTransactionInfo(kApplyToken2,
+                                  {
+                                          createComposerState(kLayerId1,
+                                                              fence(Fence::Status::Signaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken3,
+                                  {
+                                          createComposerState(kLayerId2,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+
+    setTransactionStates({signaledTransaction, signaledTransaction2, unsignaledTransaction},
+                         kExpectedTransactionsPending);
+}
+
 class LatchUnsignaledDisabledTest : public LatchUnsignaledTest {
 public:
     void SetUp() override {
@@ -943,6 +1021,43 @@
                          kExpectedTransactionsPending);
 }
 
+TEST_F(LatchUnsignaledAlwaysTest, RespectsBackPressureFlag) {
+    const sp<IBinder> kApplyToken1 =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const sp<IBinder> kApplyToken2 = sp<BBinder>::make();
+    const auto kLayerId1 = 1;
+    const auto kExpectedTransactionsPending = 1u;
+    auto layer =
+            sp<Layer>::make(LayerCreationArgs(mFlinger.flinger(), nullptr, "TestLayer", 0, {}));
+    auto layerHandle = layer->getHandle();
+    const auto setBackPressureFlagTransaction =
+            createTransactionInfo(kApplyToken1,
+                                  {createComposerState(kLayerId1, fence(Fence::Status::Unsignaled),
+                                                       layer_state_t::eBufferChanged |
+                                                               layer_state_t::eFlagsChanged,
+                                                       {layerHandle})});
+    setTransactionStates({setBackPressureFlagTransaction}, 0u);
+
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken1,
+                                  {
+                                          createComposerState(kLayerId1,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged,
+                                                              {layerHandle}),
+                                  });
+    const auto unsignaledTransaction2 =
+            createTransactionInfo(kApplyToken1,
+                                  {
+                                          createComposerState(kLayerId1,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged,
+                                                              {layerHandle}),
+                                  });
+    setTransactionStates({unsignaledTransaction, unsignaledTransaction2},
+                         kExpectedTransactionsPending);
+}
+
 TEST_F(LatchUnsignaledAlwaysTest, LatchUnsignaledWhenEarlyOffset) {
     const sp<IBinder> kApplyToken =
             IInterface::asBinder(TransactionCompletedListener::getIInstance());
@@ -973,4 +1088,137 @@
     EXPECT_EQ(transactionsReadyToBeApplied.front().id, 42u);
 }
 
+TEST(TransactionHandlerTest, TransactionsKeepTrackOfDirectMerges) {
+    SurfaceComposerClient::Transaction transaction1, transaction2, transaction3, transaction4;
+
+    uint64_t transaction2Id = transaction2.getId();
+    uint64_t transaction3Id = transaction3.getId();
+    EXPECT_NE(transaction2Id, transaction3Id);
+
+    transaction1.merge(std::move(transaction2));
+    transaction1.merge(std::move(transaction3));
+
+    EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 2u);
+    EXPECT_EQ(transaction1.getMergedTransactionIds()[0], transaction3Id);
+    EXPECT_EQ(transaction1.getMergedTransactionIds()[1], transaction2Id);
+}
+
+TEST(TransactionHandlerTest, TransactionsKeepTrackOfIndirectMerges) {
+    SurfaceComposerClient::Transaction transaction1, transaction2, transaction3, transaction4;
+
+    uint64_t transaction2Id = transaction2.getId();
+    uint64_t transaction3Id = transaction3.getId();
+    uint64_t transaction4Id = transaction4.getId();
+    EXPECT_NE(transaction2Id, transaction3Id);
+    EXPECT_NE(transaction2Id, transaction4Id);
+    EXPECT_NE(transaction3Id, transaction4Id);
+
+    transaction4.merge(std::move(transaction2));
+    transaction4.merge(std::move(transaction3));
+
+    EXPECT_EQ(transaction4.getMergedTransactionIds().size(), 2u);
+    EXPECT_EQ(transaction4.getMergedTransactionIds()[0], transaction3Id);
+    EXPECT_EQ(transaction4.getMergedTransactionIds()[1], transaction2Id);
+
+    transaction1.merge(std::move(transaction4));
+
+    EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 3u);
+    EXPECT_EQ(transaction1.getMergedTransactionIds()[0], transaction4Id);
+    EXPECT_EQ(transaction1.getMergedTransactionIds()[1], transaction3Id);
+    EXPECT_EQ(transaction1.getMergedTransactionIds()[2], transaction2Id);
+}
+
+TEST(TransactionHandlerTest, TransactionMergesAreCleared) {
+    SurfaceComposerClient::Transaction transaction1, transaction2, transaction3;
+
+    transaction1.merge(std::move(transaction2));
+    transaction1.merge(std::move(transaction3));
+
+    EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 2u);
+
+    transaction1.clear();
+
+    EXPECT_EQ(transaction1.getMergedTransactionIds().empty(), true);
+}
+
+TEST(TransactionHandlerTest, TransactionMergesAreCapped) {
+    SurfaceComposerClient::Transaction transaction;
+    std::vector<uint64_t> mergedTransactionIds;
+
+    for (uint i = 0; i < 20u; i++) {
+        SurfaceComposerClient::Transaction transactionToMerge;
+        mergedTransactionIds.push_back(transactionToMerge.getId());
+        transaction.merge(std::move(transactionToMerge));
+    }
+
+    // Keeps latest 10 merges in order of merge recency
+    EXPECT_EQ(transaction.getMergedTransactionIds().size(), 10u);
+    for (uint i = 0; i < 10u; i++) {
+        EXPECT_EQ(transaction.getMergedTransactionIds()[i],
+                  mergedTransactionIds[mergedTransactionIds.size() - 1 - i]);
+    }
+}
+
+TEST(TransactionHandlerTest, KeepsMergesFromMoreRecentMerge) {
+    SurfaceComposerClient::Transaction transaction1, transaction2, transaction3;
+    std::vector<uint64_t> mergedTransactionIds1, mergedTransactionIds2, mergedTransactionIds3;
+    uint64_t transaction2Id = transaction2.getId();
+    uint64_t transaction3Id = transaction3.getId();
+
+    for (uint i = 0; i < 20u; i++) {
+        SurfaceComposerClient::Transaction transactionToMerge;
+        mergedTransactionIds1.push_back(transactionToMerge.getId());
+        transaction1.merge(std::move(transactionToMerge));
+    }
+
+    for (uint i = 0; i < 5u; i++) {
+        SurfaceComposerClient::Transaction transactionToMerge;
+        mergedTransactionIds2.push_back(transactionToMerge.getId());
+        transaction2.merge(std::move(transactionToMerge));
+    }
+
+    transaction1.merge(std::move(transaction2));
+    EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 10u);
+    EXPECT_EQ(transaction1.getMergedTransactionIds()[0], transaction2Id);
+    for (uint i = 0; i < 5u; i++) {
+        EXPECT_EQ(transaction1.getMergedTransactionIds()[i + 1u],
+                  mergedTransactionIds2[mergedTransactionIds2.size() - 1 - i]);
+    }
+    for (uint i = 0; i < 4u; i++) {
+        EXPECT_EQ(transaction1.getMergedTransactionIds()[i + 6u],
+                  mergedTransactionIds1[mergedTransactionIds1.size() - 1 - i]);
+    }
+
+    for (uint i = 0; i < 20u; i++) {
+        SurfaceComposerClient::Transaction transactionToMerge;
+        mergedTransactionIds3.push_back(transactionToMerge.getId());
+        transaction3.merge(std::move(transactionToMerge));
+    }
+
+    transaction1.merge(std::move(transaction3));
+    EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 10u);
+    EXPECT_EQ(transaction1.getMergedTransactionIds()[0], transaction3Id);
+    for (uint i = 0; i < 9u; i++) {
+        EXPECT_EQ(transaction1.getMergedTransactionIds()[i + 1],
+                  mergedTransactionIds3[mergedTransactionIds3.size() - 1 - i]);
+    }
+}
+
+TEST(TransactionHandlerTest, CanAddTransactionWithFullMergedIds) {
+    SurfaceComposerClient::Transaction transaction1, transaction2;
+    for (uint i = 0; i < 20u; i++) {
+        SurfaceComposerClient::Transaction transactionToMerge;
+        transaction1.merge(std::move(transactionToMerge));
+    }
+
+    EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 10u);
+
+    auto transaction1Id = transaction1.getId();
+    transaction2.merge(std::move(transaction1));
+    EXPECT_EQ(transaction2.getMergedTransactionIds().size(), 10u);
+    auto mergedTransactionIds = transaction2.getMergedTransactionIds();
+    EXPECT_TRUE(std::count(mergedTransactionIds.begin(), mergedTransactionIds.end(),
+                           transaction1Id) > 0);
+}
+
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
index b6427c0..dd72174 100644
--- a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
@@ -19,6 +19,8 @@
 #include <limits> // std::numeric_limits
 
 #include <gui/SurfaceComposerClient.h>
+#include <ui/Rotation.h>
+#include "LayerProtoHelper.h"
 
 #include "Tracing/TransactionProtoParser.h"
 
@@ -27,7 +29,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 +38,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 +48,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 +71,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 +83,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,12 +96,56 @@
     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);
     ASSERT_EQ(t1.displays[0].token, t2.displays[0].token);
 }
 
+TEST(TransactionProtoParserTest, parseDisplayInfo) {
+    frontend::DisplayInfo d1;
+    d1.info.displayId = 42;
+    d1.info.logicalWidth = 43;
+    d1.info.logicalHeight = 44;
+    d1.info.transform.set(1, 2, 3, 4);
+    d1.transform = d1.info.transform.inverse();
+    d1.receivesInput = true;
+    d1.isSecure = false;
+    d1.isPrimary = true;
+    d1.isVirtual = false;
+    d1.rotationFlags = ui::Transform::ROT_180;
+    d1.transformHint = ui::Transform::ROT_90;
+
+    const uint32_t layerStack = 2;
+    google::protobuf::RepeatedPtrField<proto::DisplayInfo> displayProtos;
+    auto displayInfoProto = displayProtos.Add();
+    *displayInfoProto = TransactionProtoParser::toProto(d1, layerStack);
+    display::DisplayMap<ui::LayerStack, frontend::DisplayInfo> displayInfos;
+    TransactionProtoParser::fromProto(displayProtos, displayInfos);
+
+    ASSERT_TRUE(displayInfos.contains(ui::LayerStack::fromValue(layerStack)));
+    frontend::DisplayInfo d2 = displayInfos.get(ui::LayerStack::fromValue(layerStack))->get();
+    EXPECT_EQ(d1.info.displayId, d2.info.displayId);
+    EXPECT_EQ(d1.info.logicalWidth, d2.info.logicalWidth);
+    EXPECT_EQ(d1.info.logicalHeight, d2.info.logicalHeight);
+
+    EXPECT_EQ(d1.info.transform.dsdx(), d2.info.transform.dsdx());
+    EXPECT_EQ(d1.info.transform.dsdy(), d2.info.transform.dsdy());
+    EXPECT_EQ(d1.info.transform.dtdx(), d2.info.transform.dtdx());
+    EXPECT_EQ(d1.info.transform.dtdy(), d2.info.transform.dtdy());
+
+    EXPECT_EQ(d1.transform.dsdx(), d2.transform.dsdx());
+    EXPECT_EQ(d1.transform.dsdy(), d2.transform.dsdy());
+    EXPECT_EQ(d1.transform.dtdx(), d2.transform.dtdx());
+    EXPECT_EQ(d1.transform.dtdy(), d2.transform.dtdy());
+
+    EXPECT_EQ(d1.receivesInput, d2.receivesInput);
+    EXPECT_EQ(d1.isSecure, d2.isSecure);
+    EXPECT_EQ(d1.isVirtual, d2.isVirtual);
+    EXPECT_EQ(d1.rotationFlags, d2.rotationFlags);
+    EXPECT_EQ(d1.transformHint, d2.transformHint);
+}
+
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
index 482c3a8..92411a7 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,31 @@
                      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);
+            const auto expectedTransaction = expectedTransactions[i];
+            const auto protoTransaction = actualProto.transactions(static_cast<int32_t>(i));
+            EXPECT_EQ(protoTransaction.transaction_id(), expectedTransaction.id);
+            EXPECT_EQ(protoTransaction.pid(), expectedTransaction.originPid);
+            for (uint32_t i = 0; i < expectedTransaction.mergedTransactionIds.size(); i++) {
+                EXPECT_EQ(protoTransaction.merged_transaction_ids(static_cast<int32_t>(i)),
+                          expectedTransaction.mergedTransactionIds[i]);
+            }
         }
     }
+
+    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) {
@@ -73,63 +98,66 @@
         TransactionState transaction;
         transaction.id = i;
         transaction.originPid = static_cast<int32_t>(i);
+        transaction.mergedTransactionIds = std::vector<uint64_t>{i + 100, i + 102};
         transactions.emplace_back(transaction);
         mTracing.addQueuedTransaction(transaction);
     }
 
     // Split incoming transactions into two and commit them in reverse order to test out of order
     // commits.
-    std::vector<TransactionState> firstTransactionSet =
-            std::vector<TransactionState>(transactions.begin() + 50, transactions.end());
     int64_t firstTransactionSetVsyncId = 42;
-    mTracing.addCommittedTransactions(firstTransactionSet, firstTransactionSetVsyncId);
+    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 +167,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 +191,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 +260,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 +314,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/VSyncCallbackRegistrationTest.cpp b/services/surfaceflinger/tests/unittests/VSyncCallbackRegistrationTest.cpp
new file mode 100644
index 0000000..69b3861
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/VSyncCallbackRegistrationTest.cpp
@@ -0,0 +1,144 @@
+/*
+ * 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "Scheduler/VSyncDispatch.h"
+#include "mock/MockVSyncDispatch.h"
+
+using namespace testing;
+
+namespace android::scheduler {
+
+class VSyncCallbackRegistrationTest : public Test {
+protected:
+    VSyncDispatch::Callback mCallback = [](nsecs_t, nsecs_t, nsecs_t) {};
+
+    std::shared_ptr<mock::VSyncDispatch> mVsyncDispatch = std::make_shared<mock::VSyncDispatch>();
+    VSyncDispatch::CallbackToken mCallbackToken{7};
+    std::string mCallbackName = "callback";
+
+    std::shared_ptr<mock::VSyncDispatch> mVsyncDispatch2 = std::make_shared<mock::VSyncDispatch>();
+    VSyncDispatch::CallbackToken mCallbackToken2{42};
+    std::string mCallbackName2 = "callback2";
+
+    void assertDispatch(const VSyncCallbackRegistration& registration,
+                        std::shared_ptr<VSyncDispatch> dispatch) {
+        ASSERT_EQ(registration.mDispatch, dispatch);
+    }
+
+    void assertToken(const VSyncCallbackRegistration& registration,
+                     const std::optional<VSyncDispatch::CallbackToken>& token) {
+        ASSERT_EQ(registration.mToken, token);
+    }
+};
+
+TEST_F(VSyncCallbackRegistrationTest, unregistersCallbackOnDestruction) {
+    // TODO (b/279581095): With ftl::Function, `_` can be replaced with
+    // `mCallback`, here and in other calls to `registerCallback, since the
+    // ftl version has an operator==, unlike std::function.
+    EXPECT_CALL(*mVsyncDispatch, registerCallback(_, mCallbackName))
+            .WillOnce(Return(mCallbackToken));
+    EXPECT_CALL(*mVsyncDispatch, unregisterCallback(mCallbackToken)).Times(1);
+
+    VSyncCallbackRegistration registration(mVsyncDispatch, mCallback, mCallbackName);
+    ASSERT_NO_FATAL_FAILURE(assertDispatch(registration, mVsyncDispatch));
+    ASSERT_NO_FATAL_FAILURE(assertToken(registration, mCallbackToken));
+}
+
+TEST_F(VSyncCallbackRegistrationTest, unregistersCallbackOnPointerMove) {
+    {
+        InSequence seq;
+        EXPECT_CALL(*mVsyncDispatch, registerCallback(_, mCallbackName))
+                .WillOnce(Return(mCallbackToken));
+        EXPECT_CALL(*mVsyncDispatch2, registerCallback(_, mCallbackName2))
+                .WillOnce(Return(mCallbackToken2));
+        EXPECT_CALL(*mVsyncDispatch2, unregisterCallback(mCallbackToken2)).Times(1);
+        EXPECT_CALL(*mVsyncDispatch, unregisterCallback(mCallbackToken)).Times(1);
+    }
+
+    auto registration =
+            std::make_unique<VSyncCallbackRegistration>(mVsyncDispatch, mCallback, mCallbackName);
+
+    auto registration2 =
+            std::make_unique<VSyncCallbackRegistration>(mVsyncDispatch2, mCallback, mCallbackName2);
+
+    registration2 = std::move(registration);
+
+    ASSERT_NO_FATAL_FAILURE(assertDispatch(*registration2.get(), mVsyncDispatch));
+    ASSERT_NO_FATAL_FAILURE(assertToken(*registration2.get(), mCallbackToken));
+}
+
+TEST_F(VSyncCallbackRegistrationTest, unregistersCallbackOnMoveOperator) {
+    {
+        InSequence seq;
+        EXPECT_CALL(*mVsyncDispatch, registerCallback(_, mCallbackName))
+                .WillOnce(Return(mCallbackToken));
+        EXPECT_CALL(*mVsyncDispatch2, registerCallback(_, mCallbackName2))
+                .WillOnce(Return(mCallbackToken2));
+        EXPECT_CALL(*mVsyncDispatch2, unregisterCallback(mCallbackToken2)).Times(1);
+        EXPECT_CALL(*mVsyncDispatch, unregisterCallback(mCallbackToken)).Times(1);
+    }
+
+    VSyncCallbackRegistration registration(mVsyncDispatch, mCallback, mCallbackName);
+
+    VSyncCallbackRegistration registration2(mVsyncDispatch2, mCallback, mCallbackName2);
+
+    registration2 = std::move(registration);
+
+    ASSERT_NO_FATAL_FAILURE(assertDispatch(registration, nullptr));
+    ASSERT_NO_FATAL_FAILURE(assertToken(registration, std::nullopt));
+
+    ASSERT_NO_FATAL_FAILURE(assertDispatch(registration2, mVsyncDispatch));
+    ASSERT_NO_FATAL_FAILURE(assertToken(registration2, mCallbackToken));
+}
+
+TEST_F(VSyncCallbackRegistrationTest, moveConstructor) {
+    EXPECT_CALL(*mVsyncDispatch, registerCallback(_, mCallbackName))
+            .WillOnce(Return(mCallbackToken));
+    EXPECT_CALL(*mVsyncDispatch, unregisterCallback(mCallbackToken)).Times(1);
+
+    VSyncCallbackRegistration registration(mVsyncDispatch, mCallback, mCallbackName);
+    VSyncCallbackRegistration registration2(std::move(registration));
+
+    ASSERT_NO_FATAL_FAILURE(assertDispatch(registration, nullptr));
+    ASSERT_NO_FATAL_FAILURE(assertToken(registration, std::nullopt));
+
+    ASSERT_NO_FATAL_FAILURE(assertDispatch(registration2, mVsyncDispatch));
+    ASSERT_NO_FATAL_FAILURE(assertToken(registration2, mCallbackToken));
+}
+
+TEST_F(VSyncCallbackRegistrationTest, moveOperatorEqualsSelf) {
+    EXPECT_CALL(*mVsyncDispatch, registerCallback(_, mCallbackName))
+            .WillOnce(Return(mCallbackToken));
+    EXPECT_CALL(*mVsyncDispatch, unregisterCallback(mCallbackToken)).Times(1);
+
+    VSyncCallbackRegistration registration(mVsyncDispatch, mCallback, mCallbackName);
+
+    // Use a reference so the compiler doesn't realize that registration is
+    // being moved to itself.
+    VSyncCallbackRegistration& registrationRef = registration;
+    registration = std::move(registrationRef);
+
+    ASSERT_NO_FATAL_FAILURE(assertDispatch(registration, mVsyncDispatch));
+    ASSERT_NO_FATAL_FAILURE(assertToken(registration, mCallbackToken));
+}
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h
deleted file mode 100644
index 5654691..0000000
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <gmock/gmock.h>
-#include <scheduler/Time.h>
-
-#include "DisplayHardware/PowerAdvisor.h"
-
-namespace android {
-namespace hardware {
-namespace power {
-class IPower;
-}
-} // namespace hardware
-} // namespace android
-
-namespace android::Hwc2::mock {
-
-class MockAidlPowerHalWrapper : public Hwc2::impl::AidlPowerHalWrapper {
-public:
-    MockAidlPowerHalWrapper();
-    ~MockAidlPowerHalWrapper() override;
-    MOCK_METHOD(bool, setExpensiveRendering, (bool enabled), (override));
-    MOCK_METHOD(bool, notifyDisplayUpdateImminentAndCpuReset, (), (override));
-    MOCK_METHOD(bool, supportsPowerHintSession, (), (override));
-    MOCK_METHOD(bool, isPowerHintSessionRunning, (), (override));
-    MOCK_METHOD(void, restartPowerHintSession, (), (override));
-    MOCK_METHOD(void, setPowerHintSessionThreadIds, (const std::vector<int32_t>& threadIds),
-                (override));
-    MOCK_METHOD(bool, startPowerHintSession, (), (override));
-    MOCK_METHOD(void, setTargetWorkDuration, (Duration targetDuration), (override));
-    MOCK_METHOD(void, sendActualWorkDuration, (Duration actualDuration, TimePoint timestamp),
-                (override));
-    MOCK_METHOD(bool, shouldReconnectHAL, (), (override));
-};
-
-} // namespace android::Hwc2::mock
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index 5dc3490..d3fb9fc 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -55,7 +55,6 @@
                  std::vector<aidl::android::hardware::graphics::composer3::Capability>());
     MOCK_METHOD0(dumpDebugInfo, std::string());
     MOCK_METHOD1(registerCallback, void(HWC2::ComposerCallback&));
-    MOCK_METHOD1(resetCommands, void(Display));
     MOCK_METHOD1(executeCommands, Error(Display));
     MOCK_METHOD0(getMaxVirtualDisplayCount, uint32_t());
     MOCK_METHOD4(createVirtualDisplay, Error(uint32_t, uint32_t, PixelFormat*, Display*));
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
index 7fc625c..3caa2b9 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
@@ -35,11 +35,10 @@
     MOCK_METHOD(void, notifyDisplayUpdateImminentAndCpuReset, (), (override));
     MOCK_METHOD(bool, usePowerHintSession, (), (override));
     MOCK_METHOD(bool, supportsPowerHintSession, (), (override));
-    MOCK_METHOD(bool, isPowerHintSessionRunning, (), (override));
-    MOCK_METHOD(void, setTargetWorkDuration, (Duration targetDuration), (override));
-    MOCK_METHOD(void, sendActualWorkDuration, (), (override));
-    MOCK_METHOD(void, sendPredictedWorkDuration, (), (override));
-    MOCK_METHOD(void, enablePowerHint, (bool enabled), (override));
+    MOCK_METHOD(bool, ensurePowerHintSessionRunning, (), (override));
+    MOCK_METHOD(void, updateTargetWorkDuration, (Duration targetDuration), (override));
+    MOCK_METHOD(void, reportActualWorkDuration, (), (override));
+    MOCK_METHOD(void, enablePowerHintSession, (bool enabled), (override));
     MOCK_METHOD(bool, startPowerHintSession, (const std::vector<int32_t>& threadIds), (override));
     MOCK_METHOD(void, setGpuFenceTime,
                 (DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime), (override));
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.cpp b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.cpp
similarity index 67%
rename from services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.cpp
rename to services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.cpp
index 5049b1d..3ec5c2d 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.cpp
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,13 +14,11 @@
  * limitations under the License.
  */
 
-#include "MockAidlPowerHalWrapper.h"
-#include "MockIPower.h"
+#include "MockPowerHalController.h"
 
 namespace android::Hwc2::mock {
 
-MockAidlPowerHalWrapper::MockAidlPowerHalWrapper()
-      : AidlPowerHalWrapper(sp<testing::NiceMock<MockIPower>>::make()){};
-MockAidlPowerHalWrapper::~MockAidlPowerHalWrapper() = default;
+MockPowerHalController::MockPowerHalController() = default;
+MockPowerHalController::~MockPowerHalController() = default;
 
 } // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
new file mode 100644
index 0000000..358395d
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <gmock/gmock.h>
+#include <scheduler/Time.h>
+
+#include <powermanager/PowerHalController.h>
+
+namespace android {
+namespace hardware {
+namespace power {
+class IPower;
+}
+} // namespace hardware
+} // namespace android
+
+namespace android::Hwc2::mock {
+
+using android::hardware::power::Boost;
+using android::hardware::power::Mode;
+using android::power::HalResult;
+
+class MockPowerHalController : public power::PowerHalController {
+public:
+    MockPowerHalController();
+    ~MockPowerHalController() override;
+    MOCK_METHOD(HalResult<void>, setBoost, (Boost, int32_t), (override));
+    MOCK_METHOD(HalResult<void>, setMode, (Mode, bool), (override));
+    MOCK_METHOD(HalResult<sp<hardware::power::IPowerHintSession>>, createHintSession,
+                (int32_t, int32_t, const std::vector<int32_t>&, int64_t), (override));
+    MOCK_METHOD(HalResult<int64_t>, getHintSessionPreferredRate, (), (override));
+};
+
+} // namespace android::Hwc2::mock
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/mock/MockLayer.h b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
index 0d94f4c..50e07fc 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockLayer.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
@@ -32,7 +32,7 @@
     MOCK_CONST_METHOD0(getType, const char*());
     MOCK_METHOD0(getFrameSelectionPriority, int32_t());
     MOCK_CONST_METHOD0(isVisible, bool());
-    MOCK_METHOD0(createClone, sp<Layer>());
+    MOCK_METHOD1(createClone, sp<Layer>(uint32_t));
     MOCK_CONST_METHOD0(getFrameRateForLayerTree, FrameRate());
     MOCK_CONST_METHOD0(getDefaultFrameRateCompatibility,
                        scheduler::LayerInfo::FrameRateCompatibility());
diff --git a/services/surfaceflinger/tests/utils/WindowInfosListenerUtils.h b/services/surfaceflinger/tests/utils/WindowInfosListenerUtils.h
new file mode 100644
index 0000000..8e28a75
--- /dev/null
+++ b/services/surfaceflinger/tests/utils/WindowInfosListenerUtils.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+#include <gui/SurfaceComposerClient.h>
+#include <private/android_filesystem_config.h>
+#include <cstdint>
+#include <future>
+
+namespace android {
+using Transaction = SurfaceComposerClient::Transaction;
+using gui::DisplayInfo;
+using gui::WindowInfo;
+
+using WindowInfosPredicate = std::function<bool(const std::vector<WindowInfo>&)>;
+
+class WindowInfosListenerUtils {
+public:
+    WindowInfosListenerUtils() { mClient = sp<SurfaceComposerClient>::make(); }
+
+    bool waitForWindowInfosPredicate(const WindowInfosPredicate& predicate) {
+        std::promise<void> promise;
+        auto listener = sp<WindowInfosListener>::make(std::move(predicate), promise);
+        mClient->addWindowInfosListener(listener);
+        auto future = promise.get_future();
+        bool satisfied = future.wait_for(std::chrono::seconds{1}) == std::future_status::ready;
+        mClient->removeWindowInfosListener(listener);
+        return satisfied;
+    }
+
+    static const WindowInfo* findMatchingWindowInfo(const WindowInfo& targetWindowInfo,
+                                                    const std::vector<WindowInfo>& windowInfos) {
+        for (const WindowInfo& windowInfo : windowInfos) {
+            if (windowInfo.token == targetWindowInfo.token) {
+                return &windowInfo;
+            }
+        }
+        return nullptr;
+    }
+
+private:
+    struct WindowInfosListener : public gui::WindowInfosListener {
+    public:
+        WindowInfosListener(WindowInfosPredicate predicate, std::promise<void>& promise)
+              : mPredicate(std::move(predicate)), mPromise(promise) {}
+
+        void onWindowInfosChanged(const gui::WindowInfosUpdate& update) override {
+            if (mPredicate(update.windowInfos)) {
+                mPromise.set_value();
+            }
+        }
+
+    private:
+        WindowInfosPredicate mPredicate;
+        std::promise<void>& mPromise;
+    };
+
+    sp<SurfaceComposerClient> mClient;
+};
+
+} // namespace android
diff --git a/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp b/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp
index 53f3daf..971a0b9 100644
--- a/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp
+++ b/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "PowerHalControllerBenchmarks"
+#define LOG_TAG "VibratorHalControllerBenchmarks"
 
 #include <benchmark/benchmark.h>
 #include <vibratorservice/VibratorHalController.h>
@@ -183,7 +183,7 @@
         return;
     }
 
-    auto duration = 6000s;
+    auto duration = 60s;
     auto callback = []() {};
     auto amplitude = 1.0f;
 
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 1bff50d..5965953 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -227,6 +227,10 @@
     android::sp<ANativeWindow> window;
     VkSwapchainKHR swapchain_handle;
     uint64_t consumer_usage;
+
+    // Indicate whether this surface has been used by a swapchain, no matter the
+    // swapchain is still current or has been destroyed.
+    bool used_by_swapchain;
 };
 
 VkSurfaceKHR HandleFromSurface(Surface* surface) {
@@ -252,27 +256,31 @@
     Swapchain(Surface& surface_,
               uint32_t num_images_,
               VkPresentModeKHR present_mode,
-              int pre_transform_)
+              int pre_transform_,
+              int64_t refresh_duration_)
         : surface(surface_),
           num_images(num_images_),
           mailbox_mode(present_mode == VK_PRESENT_MODE_MAILBOX_KHR),
           pre_transform(pre_transform_),
           frame_timestamps_enabled(false),
+          refresh_duration(refresh_duration_),
           acquire_next_image_timeout(-1),
           shared(IsSharedPresentMode(present_mode)) {
-        ANativeWindow* window = surface.window.get();
-        native_window_get_refresh_cycle_duration(
-            window,
-            &refresh_duration);
     }
-    uint64_t get_refresh_duration()
+
+    VkResult get_refresh_duration(uint64_t& outRefreshDuration)
     {
         ANativeWindow* window = surface.window.get();
-        native_window_get_refresh_cycle_duration(
+        int err = native_window_get_refresh_cycle_duration(
             window,
             &refresh_duration);
-        return static_cast<uint64_t>(refresh_duration);
-
+        if (err != android::OK) {
+            ALOGE("%s:native_window_get_refresh_cycle_duration failed: %s (%d)",
+                __func__, strerror(-err), err );
+            return VK_ERROR_SURFACE_LOST_KHR;
+        }
+        outRefreshDuration = refresh_duration;
+        return VK_SUCCESS;
     }
 
     Surface& surface;
@@ -514,7 +522,6 @@
         case VK_FORMAT_R8_UNORM:
             native_format = android::PIXEL_FORMAT_R_8;
             break;
-        // TODO: Do we need to query for VK_EXT_rgba10x6_formats here?
         case VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16:
             native_format = android::PIXEL_FORMAT_RGBA_10101010;
             break;
@@ -598,6 +605,7 @@
 
     surface->window = pCreateInfo->window;
     surface->swapchain_handle = VK_NULL_HANDLE;
+    surface->used_by_swapchain = false;
     int err = native_window_get_consumer_usage(surface->window.get(),
                                                &surface->consumer_usage);
     if (err != android::OK) {
@@ -805,9 +813,23 @@
         }
     }
 
-    // TODO query VK_EXT_rgba10x6_formats support
+    bool rgba10x6_formats_ext = false;
+    uint32_t exts_count;
+    const auto& driver = GetData(pdev).driver;
+    driver.EnumerateDeviceExtensionProperties(pdev, nullptr, &exts_count,
+                                              nullptr);
+    std::vector<VkExtensionProperties> props(exts_count);
+    driver.EnumerateDeviceExtensionProperties(pdev, nullptr, &exts_count,
+                                              props.data());
+    for (uint32_t i = 0; i < exts_count; i++) {
+        VkExtensionProperties prop = props[i];
+        if (strcmp(prop.extensionName,
+                   VK_EXT_RGBA10X6_FORMATS_EXTENSION_NAME) == 0) {
+            rgba10x6_formats_ext = true;
+        }
+    }
     desc.format = AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM;
-    if (AHardwareBuffer_isSupported(&desc)) {
+    if (AHardwareBuffer_isSupported(&desc) && rgba10x6_formats_ext) {
         all_formats.emplace_back(
             VkSurfaceFormatKHR{VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16,
                                VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
@@ -1377,14 +1399,20 @@
     // orphans the previous buffers, getting us back to the state where we can
     // dequeue all buffers.
     //
+    // This is not necessary if the surface was never used previously.
+    //
     // TODO(http://b/134186185) recycle swapchain images more efficiently
     ANativeWindow* window = surface.window.get();
-    err = native_window_api_disconnect(window, NATIVE_WINDOW_API_EGL);
-    ALOGW_IF(err != android::OK, "native_window_api_disconnect failed: %s (%d)",
-             strerror(-err), err);
-    err = native_window_api_connect(window, NATIVE_WINDOW_API_EGL);
-    ALOGW_IF(err != android::OK, "native_window_api_connect failed: %s (%d)",
-             strerror(-err), err);
+    if (surface.used_by_swapchain) {
+        err = native_window_api_disconnect(window, NATIVE_WINDOW_API_EGL);
+        ALOGW_IF(err != android::OK,
+                 "native_window_api_disconnect failed: %s (%d)", strerror(-err),
+                 err);
+        err = native_window_api_connect(window, NATIVE_WINDOW_API_EGL);
+        ALOGW_IF(err != android::OK,
+                 "native_window_api_connect failed: %s (%d)", strerror(-err),
+                 err);
+    }
 
     err =
         window->perform(window, NATIVE_WINDOW_SET_DEQUEUE_TIMEOUT, nsecs_t{-1});
@@ -1613,6 +1641,13 @@
         return VK_ERROR_SURFACE_LOST_KHR;
     }
 
+    int64_t refresh_duration;
+    err = native_window_get_refresh_cycle_duration(window, &refresh_duration);
+    if (err != android::OK) {
+        ALOGE("native_window_get_refresh_cycle_duration query failed: %s (%d)",
+              strerror(-err), err);
+        return VK_ERROR_SURFACE_LOST_KHR;
+    }
     // -- Allocate our Swapchain object --
     // After this point, we must deallocate the swapchain on error.
 
@@ -1623,8 +1658,8 @@
         return VK_ERROR_OUT_OF_HOST_MEMORY;
     Swapchain* swapchain = new (mem)
         Swapchain(surface, num_images, create_info->presentMode,
-                  TranslateVulkanToNativeTransform(create_info->preTransform));
-
+                  TranslateVulkanToNativeTransform(create_info->preTransform),
+                  refresh_duration);
     VkSwapchainImageCreateInfoANDROID swapchain_image_create = {
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wold-style-cast"
@@ -1763,6 +1798,7 @@
     android::GraphicsEnv::getInstance().setTargetStats(
         android::GpuStatsInfo::Stats::CREATED_VULKAN_SWAPCHAIN);
 
+    surface.used_by_swapchain = true;
     surface.swapchain_handle = HandleFromSwapchain(swapchain);
     *swapchain_handle = surface.swapchain_handle;
     return VK_SUCCESS;
@@ -2295,9 +2331,7 @@
     ATRACE_CALL();
 
     Swapchain& swapchain = *SwapchainFromHandle(swapchain_handle);
-    VkResult result = VK_SUCCESS;
-
-    pDisplayTimingProperties->refreshDuration = swapchain.get_refresh_duration();
+    VkResult result = swapchain.get_refresh_duration(pDisplayTimingProperties->refreshDuration);
 
     return result;
 }