Merge "Revert "Only call onNewVsyncSchedule if the pacesetter changes"" into udc-dev
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 39eb4ab..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/
@@ -41,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 6f84e7c..8ca927e 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,7 +1256,8 @@
              dumpsys.writeDumpHeader(STDOUT_FILENO, service, priority);
              dumpsys.writeDumpFooter(STDOUT_FILENO, service, std::chrono::milliseconds(1));
         } else {
-             status_t status = dumpsys.startDumpThread(Dumpsys::TYPE_DUMP | Dumpsys::TYPE_PID,
+             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);
@@ -1273,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);
             }
         }
 
@@ -2066,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) {
@@ -2198,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)) {
@@ -2228,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
@@ -2808,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;
@@ -3353,8 +3371,7 @@
              "WMShell", "protolog", "save-for-bugreport"},
         CommandOptions::WithTimeout(10).Always().DropRoot().RedirectStderr().Build());
 
-    // Currently WindowManagerService and InputMethodManagerSerivice support WinScope protocol.
-    for (const auto& service : {"input_method", "window"}) {
+    for (const auto& service : {"input_method", "window", "window shell"}) {
         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.
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..93d8cdf 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);
diff --git a/cmds/installd/dexopt.cpp b/cmds/installd/dexopt.cpp
index 794750f..1693ed5 100644
--- a/cmds/installd/dexopt.cpp
+++ b/cmds/installd/dexopt.cpp
@@ -1942,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;
@@ -1964,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 bf2c0d1..7cabdb0 100644
--- a/cmds/installd/otapreopt.cpp
+++ b/cmds/installd/otapreopt.cpp
@@ -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/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 5c4e1a4..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());
 }
@@ -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(
@@ -962,6 +974,10 @@
 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,
@@ -976,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();
@@ -998,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);
@@ -1063,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);
@@ -1436,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;
@@ -1449,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/servicemanager/Android.bp b/cmds/servicemanager/Android.bp
index 1386660..d73a30b 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: [
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index 4da0cd6..63f3821 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -337,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);
@@ -418,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;
@@ -462,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);
@@ -493,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;
@@ -505,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();
@@ -515,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;
@@ -543,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();
@@ -554,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;
@@ -583,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();
@@ -594,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;
@@ -663,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);
@@ -806,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;
@@ -831,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)
@@ -853,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());
@@ -864,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/data/etc/Android.bp b/data/etc/Android.bp
index bdd5172..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"],
@@ -167,6 +257,12 @@
 }
 
 prebuilt_etc {
+    name: "android.hardware.telephony.satellite.prebuilt.xml",
+    src: "android.hardware.telephony.satellite.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "android.hardware.usb.accessory.prebuilt.xml",
     src: "android.hardware.usb.accessory.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
diff --git a/data/etc/android.hardware.telephony.satellite.xml b/data/etc/android.hardware.telephony.satellite.xml
new file mode 100644
index 0000000..d36c958
--- /dev/null
+++ b/data/etc/android.hardware.telephony.satellite.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Feature for devices that support satellite communication via satellite vendor service APIs. -->
+<permissions>
+    <feature name="android.hardware.telephony.satellite" />
+</permissions>
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..daeebec 100644
--- a/include/android/surface_control.h
+++ b/include/android/surface_control.h
@@ -555,6 +555,8 @@
  *                     range in use.
  *
  *                     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/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/input/Input.h b/include/input/Input.h
index e8af5f7..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;
     }
 
@@ -887,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; }
 
@@ -906,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; }
 
@@ -925,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; }
 
@@ -949,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/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..9423041 100644
--- a/include/input/KeyCharacterMap.h
+++ b/include/input/KeyCharacterMap.h
@@ -227,7 +227,7 @@
         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);
     };
@@ -243,7 +243,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 18ce16d..2e44142 100644
--- a/include/input/PropertyMap.h
+++ b/include/input/PropertyMap.h
@@ -19,6 +19,7 @@
 #include <android-base/result.h>
 #include <utils/Tokenizer.h>
 
+#include <optional>
 #include <string>
 #include <unordered_map>
 #include <unordered_set>
@@ -63,15 +64,15 @@
     /* 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 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.)
+    /* 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;
-    bool tryGetProperty(const std::string& key, double& 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/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/IActivityManager.cpp b/libs/binder/IActivityManager.cpp
index 08169f5..ebdaa4c 100644
--- a/libs/binder/IActivityManager.cpp
+++ b/libs/binder/IActivityManager.cpp
@@ -131,6 +131,53 @@
         *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);
+        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);
+        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);
+        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/RecordedTransaction.cpp b/libs/binder/RecordedTransaction.cpp
index ef58ed3..1c76135 100644
--- a/libs/binder/RecordedTransaction.cpp
+++ b/libs/binder/RecordedTransaction.cpp
@@ -131,12 +131,12 @@
         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;
     }
@@ -349,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/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 07b38d7..41707d4 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": [
diff --git a/libs/binder/include_activitymanager/binder/IActivityManager.h b/libs/binder/include_activitymanager/binder/IActivityManager.h
index 4632b2e..20d12ae 100644
--- a/libs/binder/include_activitymanager/binder/IActivityManager.h
+++ b/libs/binder/include_activitymanager/binder/IActivityManager.h
@@ -42,6 +42,10 @@
                                     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,
@@ -50,6 +54,9 @@
         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_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/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/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index efc314b..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
 
@@ -214,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)) {
@@ -232,22 +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";
-    } else {
-        ret += "_multi_threaded";
-    }
-    if (noKernel) {
-        ret += "_no_kernel";
-    } else {
-        ret += "_with_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
@@ -321,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)));
     }
@@ -391,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();
@@ -477,7 +473,9 @@
     constexpr size_t kNumThreads = 10;
     constexpr size_t kNumCalls = kNumThreads + 3;
     auto proc = createRpcTestSocketServerProcess({.numThreads = kNumThreads});
-    testThreadPoolOverSaturated(proc.rootIface, kNumCalls, 250 /*ms*/);
+
+    // b/272429574 - below 500ms, the test fails
+    testThreadPoolOverSaturated(proc.rootIface, kNumCalls, 500 /*ms*/);
 }
 
 TEST_P(BinderRpc, ThreadPoolLimitOutgoing) {
@@ -490,7 +488,9 @@
     constexpr size_t kNumCalls = kNumOutgoingConnections + 3;
     auto proc = createRpcTestSocketServerProcess(
             {.numThreads = kNumThreads, .numOutgoingConnections = kNumOutgoingConnections});
-    testThreadPoolOverSaturated(proc.rootIface, kNumCalls, 250 /*ms*/);
+
+    // b/272429574 - below 500ms, the test fails
+    testThreadPoolOverSaturated(proc.rootIface, kNumCalls, 500 /*ms*/);
 }
 
 TEST_P(BinderRpc, ThreadingStressTest) {
@@ -1111,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{
@@ -1215,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()),
@@ -1239,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>> {};
 
@@ -1257,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);
@@ -1353,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));
     }
 };
@@ -1361,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)));
@@ -1377,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>();
@@ -1426,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: {
@@ -1505,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();
@@ -1610,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();
         }
@@ -1682,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;
@@ -2015,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 a9f0df3..c1364dd 100644
--- a/libs/binder/tests/binderRpcTestCommon.h
+++ b/libs/binder/tests/binderRpcTestCommon.h
@@ -174,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) {
diff --git a/libs/binder/tests/binderRpcTestFixture.h b/libs/binder/tests/binderRpcTestFixture.h
index 20fb6bf..6cde9f7 100644
--- a/libs/binder/tests/binderRpcTestFixture.h
+++ b/libs/binder/tests/binderRpcTestFixture.h
@@ -148,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 ca5a117..a9736d5 100644
--- a/libs/binder/tests/binderRpcTestService.cpp
+++ b/libs/binder/tests/binderRpcTestService.cpp
@@ -116,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 85794bd..28be10d 100644
--- a/libs/binder/tests/binderRpcTestTrusty.cpp
+++ b/libs/binder/tests/binderRpcTestTrusty.cpp
@@ -39,31 +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";
-    } else {
-        ret += "_multi_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";
-    } else {
-        ret += "_with_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.numIncomingConnectionsBySession.size() != 0,
-                        "Non-zero incoming connections %zu on Trusty",
-                        options.numIncomingConnectionsBySession.size());
+    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());
 
@@ -71,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/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/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/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/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/gui/Android.bp b/libs/gui/Android.bp
index 21900a0..0a63c15 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -66,6 +66,18 @@
     ],
 }
 
+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",
+    ],
+}
+
 cc_library_static {
     name: "libgui_window_info_static",
     vendor_available: true,
@@ -118,6 +130,9 @@
     name: "libgui_aidl",
     srcs: ["aidl/**/*.aidl"],
     path: "aidl/",
+    aidl: {
+        deps: [":android_gui_aidl"],
+    },
 }
 
 filegroup {
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 821dd37..5c324b2 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -582,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);
@@ -800,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() {
@@ -835,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.
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index b391337..fee91a4 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);
+    }
     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);
+    }
     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/Surface.cpp b/libs/gui/Surface.cpp
index b18bf5b..a5cf8d6 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -793,11 +793,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) {
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 2f5830d..7700aa4 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -1723,8 +1723,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;
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/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/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 69e9f8a..a49a859 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -97,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);
diff --git a/libs/gui/include/gui/DisplayCaptureArgs.h b/libs/gui/include/gui/DisplayCaptureArgs.h
index c826c17..5c794ae 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 {
 
@@ -55,6 +57,8 @@
 
     bool grayscale = false;
 
+    std::unordered_set<sp<IBinder>, SpHash<IBinder>> excludeHandles;
+
     virtual status_t writeToParcel(Parcel* output) const;
     virtual status_t readFromParcel(const Parcel* input);
 };
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..5c88a07 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -389,8 +389,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/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/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/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/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/Input.cpp b/libs/input/Input.cpp
index 53b22cb..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]]
@@ -189,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";
@@ -256,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) {
@@ -306,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) {
@@ -810,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) {
@@ -866,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();
@@ -1030,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) {
@@ -1184,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..f703901 100644
--- a/libs/input/KeyCharacterMap.cpp
+++ b/libs/input/KeyCharacterMap.cpp
@@ -272,8 +272,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 +284,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 +332,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.
@@ -493,19 +493,18 @@
     return std::make_pair(toKeyCode, toMetaState);
 }
 
-bool KeyCharacterMap::getKey(int32_t keyCode, const Key** outKey) const {
+const KeyCharacterMap::Key* KeyCharacterMap::getKey(int32_t keyCode) const {
     ssize_t index = mKeys.indexOfKey(keyCode);
     if (index >= 0) {
-        *outKey = mKeys.valueAt(index);
-        return true;
+        return mKeys.valueAt(index);
     }
-    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;
@@ -1048,17 +1047,17 @@
     String8 token = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER);
     if (token == "}") {
         mState = STATE_TOP;
-        return finishKey(key);
+        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 +1066,7 @@
                         mTokenizer->getLocation().string(), token.string());
                 return status;
             }
-            properties.add(Property(PROPERTY_META, metaState));
+            properties.emplace_back(PROPERTY_META, metaState);
         }
 
         mTokenizer->skipDelimiters(WHITESPACE);
@@ -1182,8 +1181,7 @@
     } 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) {
@@ -1230,12 +1228,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 +1245,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 9a4f10b..548f894 100644
--- a/libs/input/PropertyMap.cpp
+++ b/libs/input/PropertyMap.cpp
@@ -60,75 +60,62 @@
     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;
 }
 
-bool PropertyMap::tryGetProperty(const std::string& key, double& outValue) const {
-    std::string stringValue;
-    if (!tryGetProperty(key, stringValue) || stringValue.length() == 0) {
-        return false;
+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);
+    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 false;
+              stringValue->c_str());
+        return std::nullopt;
     }
-    outValue = value;
-    return true;
+    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/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp
index 3c1f2b6..af9ce3a 100644
--- a/libs/input/VirtualInputDevice.cpp
+++ b/libs/input/VirtualInputDevice.cpp
@@ -44,15 +44,24 @@
     ioctl(mFd, UI_DEV_DESTROY);
 }
 
-bool VirtualInputDevice::writeInputEvent(uint16_t type, uint16_t code, int32_t value) {
-    struct input_event ev = {.type = type, .code = code, .value = 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,
+                             .input_event_sec = static_cast<time_t>(seconds.count()),
+                             .input_event_usec = static_cast<suseconds_t>(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 +72,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 +198,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 +221,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 +248,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 +307,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 +316,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 +363,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 +372,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 +387,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 2132dc1..a965573 100644
--- a/libs/input/tests/InputEvent_test.cpp
+++ b/libs/input/tests/InputEvent_test.cpp
@@ -197,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());
@@ -259,10 +259,10 @@
 
     mPointerProperties[0].clear();
     mPointerProperties[0].id = 1;
-    mPointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+    mPointerProperties[0].toolType = ToolType::FINGER;
     mPointerProperties[1].clear();
     mPointerProperties[1].id = 2;
-    mPointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    mPointerProperties[1].toolType = ToolType::STYLUS;
 
     mSamples[0].pointerCoords[0].clear();
     mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 10);
@@ -346,7 +346,7 @@
 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());
@@ -366,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());
 
@@ -692,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);
diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp
index 5d8b970..3ecf8ee 100644
--- a/libs/input/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp
@@ -98,8 +98,7 @@
 
     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);
@@ -172,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);
@@ -207,7 +206,7 @@
 
     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);
@@ -298,7 +297,7 @@
     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);
@@ -339,7 +338,7 @@
     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);
@@ -381,7 +380,7 @@
     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);
@@ -423,7 +422,7 @@
     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 7cb9526..655de80 100644
--- a/libs/input/tests/TouchResampling_test.cpp
+++ b/libs/input/tests/TouchResampling_test.cpp
@@ -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();
diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp
index 0277579..ae72109 100644
--- a/libs/input/tests/VelocityTracker_test.cpp
+++ b/libs/input/tests/VelocityTracker_test.cpp
@@ -212,7 +212,7 @@
             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);
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h
deleted file mode 100644
index 9b2dde7..0000000
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h
+++ /dev/null
@@ -1,337 +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.
-     * @param recovery_map destination of the decoded recovery map.
-     * @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,
-                         jr_uncompressed_ptr recovery_map = 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 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/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/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..932be56 100644
--- a/libs/renderengine/skia/AutoBackendTexture.cpp
+++ b/libs/renderengine/skia/AutoBackendTexture.cpp
@@ -82,6 +82,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 +119,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 +143,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/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index 5965d41..8256dd8 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -264,14 +264,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 +392,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 +462,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 +504,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);
@@ -651,28 +660,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 +878,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 ||
@@ -896,7 +915,7 @@
         // 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
+                ? display.outputDataspace
                 : layer.sourceDataspace;
 
         SkPaint paint;
@@ -904,21 +923,7 @@
             ATRACE_NAME("DrawImage");
             validateInputBufferUsage(layer.source.buffer.buffer->getBuffer());
             const auto& item = layer.source.buffer;
-            std::shared_ptr<AutoBackendTexture::LocalRef> imageTextureRef = nullptr;
-
-            if (const auto& iter = cache.find(item.buffer->getBuffer()->getId());
-                iter != cache.end()) {
-                imageTextureRef = iter->second;
-            } else {
-                // If we didn't find the image in the cache, then create a local ref but don't cache
-                // it. If we're using skia, we're guaranteed to run on a dedicated GPU thread so if
-                // we didn't find anything in the cache then we intentionally did not cache this
-                // buffer's resources.
-                imageTextureRef = std::make_shared<
-                        AutoBackendTexture::LocalRef>(grContext,
-                                                      item.buffer->getBuffer()->toAHardwareBuffer(),
-                                                      false, mTextureCleanupMgr);
-            }
+            auto imageTextureRef = getOrCreateBackendTexture(item.buffer->getBuffer(), false);
 
             // if the layer's buffer has a fence, then we must must respect the fence prior to using
             // the buffer.
@@ -991,7 +996,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 +1060,8 @@
                                                   .display = display,
                                                   .undoPremultipliedAlpha = false,
                                                   .requiresLinearEffect = requiresLinearEffect,
-                                                  .layerDimmingRatio = layerDimmingRatio}));
+                                                  .layerDimmingRatio = layerDimmingRatio,
+                                                  .outputDataSpace = runtimeEffectDataspace}));
         }
 
         if (layer.disableBlending) {
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/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 90%
rename from libs/jpegrecoverymap/Android.bp
rename to libs/ultrahdr/Android.bp
index b470f35..e3f709b 100644
--- a/libs/jpegrecoverymap/Android.bp
+++ b/libs/ultrahdr/Android.bp
@@ -22,16 +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",
     ],
@@ -49,10 +49,12 @@
 cc_library {
     name: "libjpegencoder",
     host_supported: true,
+    vendor_available: true,
 
     shared_libs: [
         "libjpeg",
         "liblog",
+        "libutils",
     ],
 
     export_include_dirs: ["include"],
@@ -65,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/jpegrecoverymap/icc.cpp b/libs/ultrahdr/icc.cpp
similarity index 92%
rename from libs/jpegrecoverymap/icc.cpp
rename to libs/ultrahdr/icc.cpp
index 5412cb1..c807705 100644
--- a/libs/jpegrecoverymap/icc.cpp
+++ b/libs/ultrahdr/icc.cpp
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#include <jpegrecoverymap/icc.h>
-#include <jpegrecoverymap/recoverymapmath.h>
+#include <ultrahdr/icc.h>
+#include <ultrahdr/gainmapmath.h>
 #include <vector>
 #include <utils/Log.h>
 
@@ -23,7 +23,7 @@
 #define FLT_MAX 0x1.fffffep127f
 #endif
 
-namespace android::jpegrecoverymap {
+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];
@@ -127,17 +127,17 @@
     }
 }
 
-std::string IccHelper::get_desc_string(const jpegr_transfer_function tf,
-                                       const jpegr_color_gamut gamut) {
+std::string IccHelper::get_desc_string(const ultrahdr_transfer_function tf,
+                                       const ultrahdr_color_gamut gamut) {
     std::string result;
     switch (gamut) {
-        case JPEGR_COLORGAMUT_BT709:
+        case ULTRAHDR_COLORGAMUT_BT709:
             result += "sRGB";
             break;
-        case JPEGR_COLORGAMUT_P3:
+        case ULTRAHDR_COLORGAMUT_P3:
             result += "Display P3";
             break;
-        case JPEGR_COLORGAMUT_BT2100:
+        case ULTRAHDR_COLORGAMUT_BT2100:
             result += "Rec2020";
             break;
         default:
@@ -146,16 +146,16 @@
     }
     result += " Gamut with ";
     switch (tf) {
-        case JPEGR_TF_SRGB:
+        case ULTRAHDR_TF_SRGB:
             result += "sRGB";
             break;
-        case JPEGR_TF_LINEAR:
+        case ULTRAHDR_TF_LINEAR:
             result += "Linear";
             break;
-        case JPEGR_TF_PQ:
+        case ULTRAHDR_TF_PQ:
             result += "PQ";
             break;
-        case JPEGR_TF_HLG:
+        case ULTRAHDR_TF_HLG:
             result += "HLG";
             break;
         default:
@@ -234,11 +234,11 @@
     return dataStruct;
 }
 
-float IccHelper::compute_tone_map_gain(const jpegr_transfer_function tf, float L) {
+float IccHelper::compute_tone_map_gain(const ultrahdr_transfer_function tf, float L) {
     if (L <= 0.f) {
         return 1.f;
     }
-    if (tf == JPEGR_TF_PQ) {
+    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].
@@ -251,7 +251,7 @@
         constexpr float kToneMapB = 1.f / kOutputMaxLuminance;
         return kInputMaxLuminance * (1.f + kToneMapA * L) / (1.f + kToneMapB * L);
     }
-    if (tf == JPEGR_TF_HLG) {
+    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);
@@ -295,7 +295,7 @@
     float L = bt2100Luminance({{{rgb[0], rgb[1], rgb[2]}}});
 
     // Compute the tone map gain based on the luminance.
-    float tone_map_gain = compute_tone_map_gain(JPEGR_TF_PQ, L);
+    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) {
@@ -397,7 +397,8 @@
     return dataStruct;
 }
 
-sp<DataStruct> IccHelper::writeIccProfile(jpegr_transfer_function tf, jpegr_color_gamut gamut) {
+sp<DataStruct> IccHelper::writeIccProfile(ultrahdr_transfer_function tf,
+                                          ultrahdr_color_gamut gamut) {
     ICCHeader header;
 
     std::vector<std::pair<uint32_t, sp<DataStruct>>> tags;
@@ -409,13 +410,13 @@
 
     Matrix3x3 toXYZD50;
     switch (gamut) {
-        case JPEGR_COLORGAMUT_BT709:
+        case ULTRAHDR_COLORGAMUT_BT709:
             toXYZD50 = kSRGB;
             break;
-        case JPEGR_COLORGAMUT_P3:
+        case ULTRAHDR_COLORGAMUT_P3:
             toXYZD50 = kDisplayP3;
             break;
-        case JPEGR_COLORGAMUT_BT2100:
+        case ULTRAHDR_COLORGAMUT_BT2100:
             toXYZD50 = kRec2020;
             break;
         default:
@@ -437,8 +438,8 @@
     tags.emplace_back(kTAG_wtpt, write_xyz_tag(kD50_x, kD50_y, kD50_z));
 
     // Compute transfer curves.
-    if (tf != JPEGR_TF_PQ) {
-        if (tf == JPEGR_TF_HLG) {
+    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) {
@@ -462,32 +463,32 @@
     }
 
     // Compute CICP.
-    if (tf == JPEGR_TF_HLG || tf == JPEGR_TF_PQ) {
+    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 == JPEGR_COLORGAMUT_BT709) {
+        if (gamut == ULTRAHDR_COLORGAMUT_BT709) {
             color_primaries = kCICPPrimariesSRGB;
-        } else if (gamut == JPEGR_COLORGAMUT_P3) {
+        } else if (gamut == ULTRAHDR_COLORGAMUT_P3) {
             color_primaries = kCICPPrimariesP3;
         }
 
         uint32_t transfer_characteristics = 0;
-        if (tf == JPEGR_TF_SRGB) {
+        if (tf == ULTRAHDR_TF_SRGB) {
             transfer_characteristics = kCICPTrfnSRGB;
-        } else if (tf == JPEGR_TF_LINEAR) {
+        } else if (tf == ULTRAHDR_TF_LINEAR) {
             transfer_characteristics = kCICPTrfnLinear;
-        } else if (tf == JPEGR_TF_PQ) {
+        } else if (tf == ULTRAHDR_TF_PQ) {
             transfer_characteristics = kCICPTrfnPQ;
-        } else if (tf == JPEGR_TF_HLG) {
+        } 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 == JPEGR_TF_PQ) {
+    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;
@@ -520,7 +521,7 @@
     }
 
     // Compute B2A0.
-    if (tf == JPEGR_TF_PQ) {
+    if (tf == ULTRAHDR_TF_PQ) {
         auto b2a_data = write_mAB_or_mBA_tag(kTAG_mBAType,
                                              /* has_a_curves */ false,
                                              /* grid_points */ nullptr,
@@ -541,7 +542,7 @@
 
     // Write the header.
     header.data_color_space = Endian_SwapBE32(Signature_RGB);
-    header.pcs = Endian_SwapBE32(tf == JPEGR_TF_PQ ? Signature_Lab : Signature_XYZ);
+    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());
 
@@ -581,4 +582,4 @@
     return dataStruct;
 }
 
-} // namespace android::jpegrecoverymap
\ No newline at end of file
+} // 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/jpegrecoverymap/include/jpegrecoverymap/icc.h b/libs/ultrahdr/include/ultrahdr/icc.h
similarity index 92%
rename from libs/jpegrecoverymap/include/jpegrecoverymap/icc.h
rename to libs/ultrahdr/include/ultrahdr/icc.h
index a81aa62..7f6ab88 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/icc.h
+++ b/libs/ultrahdr/include/ultrahdr/icc.h
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_JPEGRECOVERYMAP_ICC_H
-#define ANDROID_JPEGRECOVERYMAP_ICC_H
+#ifndef ANDROID_ULTRAHDR_ICC_H
+#define ANDROID_ULTRAHDR_ICC_H
 
-#include <jpegrecoverymap/jpegr.h>
-#include <jpegrecoverymap/jpegrutils.h>
+#include <ultrahdr/jpegr.h>
+#include <ultrahdr/jpegrutils.h>
 #include <utils/RefBase.h>
 #include <cmath>
 #include <string>
@@ -28,7 +28,7 @@
 #define USE_BIG_ENDIAN true
 #endif
 
-namespace android::jpegrecoverymap {
+namespace android::ultrahdr {
 
 typedef int32_t              Fixed;
 #define Fixed1               (1 << 16)
@@ -210,12 +210,12 @@
     static constexpr size_t kNumChannels = 3;
 
     static sp<DataStruct> write_text_tag(const char* text);
-    static std::string get_desc_string(const jpegr_transfer_function tf,
-                                       const jpegr_color_gamut gamut);
+    static 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 jpegr_transfer_function tf, float L);
+    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,
@@ -226,9 +226,9 @@
     static sp<DataStruct> write_clut(const uint8_t* grid_points, const uint8_t* grid_16);
 
 public:
-    static sp<DataStruct> writeIccProfile(const jpegr_transfer_function tf,
-                                          const jpegr_color_gamut gamut);
+    static sp<DataStruct> writeIccProfile(const ultrahdr_transfer_function tf,
+                                          const ultrahdr_color_gamut gamut);
 };
-}  // namespace android::jpegrecoverymap
+}  // namespace android::ultrahdr
 
-#endif //ANDROID_JPEGRECOVERYMAP_ICC_H
\ No newline at end of file
+#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 81%
rename from libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h
rename to libs/ultrahdr/include/ultrahdr/jpegrutils.h
index a381743..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,7 +25,7 @@
 #include <string>
 #include <cstdio>
 
-namespace android::jpegrecoverymap {
+namespace android::ultrahdr {
 
 static constexpr uint32_t EndianSwap32(uint32_t value) {
     return ((value & 0xFF) << 24) |
@@ -45,7 +45,7 @@
     #define Endian_SwapBE16(n) (n)
 #endif
 
-struct jpegr_metadata;
+struct ultrahdr_metadata_struct;
 /*
  * Mutable data structure. Holds information for metadata.
  */
@@ -87,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.
@@ -102,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>
@@ -125,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.
@@ -142,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 87%
rename from libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h
rename to libs/ultrahdr/include/ultrahdr/multipictureformat.h
index cf3387d..c5bd09d 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h
+++ b/libs/ultrahdr/include/ultrahdr/multipictureformat.h
@@ -14,17 +14,17 @@
  * 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>
 
 #ifdef USE_BIG_ENDIAN
 #undef USE_BIG_ENDIAN
 #define USE_BIG_ENDIAN true
 #endif
 
-namespace android::jpegrecoverymap {
+namespace android::ultrahdr {
 
 constexpr size_t kNumPictures = 2;
 constexpr size_t kMpEndianSize = 4;
@@ -59,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 70%
rename from libs/jpegrecoverymap/jpegr.cpp
rename to libs/ultrahdr/jpegr.cpp
index f8763c6..0f7aa27 100644
--- a/libs/jpegrecoverymap/jpegr.cpp
+++ b/libs/ultrahdr/jpegr.cpp
@@ -14,13 +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 <jpegrecoverymap/icc.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>
@@ -43,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)          \
   {                             \
@@ -61,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
@@ -86,9 +89,50 @@
   return cpuCoreCount;
 }
 
+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;
+  }
+
+  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) {
@@ -100,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));
@@ -126,9 +173,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));
 
-  sp<DataStruct> icc = IccHelper::writeIccProfile(JPEGR_TF_SRGB,
+  sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
                                                   uncompressed_yuv_420_image.colorGamut);
 
   JpegEncoderHelper jpeg_encoder;
@@ -142,7 +189,7 @@
   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;
 }
@@ -150,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) {
@@ -164,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));
@@ -189,9 +229,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));
 
-  sp<DataStruct> icc = IccHelper::writeIccProfile(JPEGR_TF_SRGB,
+  sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
                                                   uncompressed_yuv_420_image->colorGamut);
 
   JpegEncoderHelper jpeg_encoder;
@@ -205,7 +245,7 @@
   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;
 }
@@ -214,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
@@ -223,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));
@@ -248,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;
 }
@@ -258,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
@@ -266,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;
@@ -288,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));
@@ -301,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,
@@ -330,14 +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,
-                            jr_uncompressed_ptr recovery_map) {
+                            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;
   }
 
-  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)) {
@@ -352,7 +399,7 @@
     dest->width = uncompressed_rgba_image.width;
     dest->height = uncompressed_rgba_image.height;
 
-    if (recovery_map == nullptr && exif == nullptr) {
+    if (gain_map == nullptr && exif == nullptr) {
       return NO_ERROR;
     }
 
@@ -366,28 +413,40 @@
       memcpy(exif->data, jpeg_decoder.getEXIFPtr(), jpeg_decoder.getEXIFSize());
       exif->length = jpeg_decoder.getEXIFSize();
     }
-    if (recovery_map == nullptr) {
+    if (gain_map == nullptr) {
       return NO_ERROR;
     }
   }
 
   jpegr_compressed_struct compressed_map;
-  JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
+  JPEGR_CHECK(extractGainMap(compressed_jpegr_image, &compressed_map));
 
-  JpegDecoderHelper recovery_map_decoder;
-  if (!recovery_map_decoder.decompressImage(compressed_map.data, compressed_map.length)) {
+  JpegDecoderHelper gain_map_decoder;
+  if (!gain_map_decoder.decompressImage(compressed_map.data, compressed_map.length)) {
     return ERROR_JPEGR_DECODE_ERROR;
   }
 
-  if (recovery_map != nullptr) {
-    recovery_map->width = recovery_map_decoder.getDecompressedImageWidth();
-    recovery_map->height = recovery_map_decoder.getDecompressedImageHeight();
-    int size = recovery_map->width * recovery_map->height;
-    recovery_map->data = malloc(size);
-    memcpy(recovery_map->data, recovery_map_decoder.getDecompressedImagePtr(), size);
+  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);
   }
 
-  if (output_format == JPEGR_OUTPUT_SDR) {
+  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;
   }
 
@@ -408,35 +467,30 @@
   }
 
   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();
 
-  jpegr_metadata metadata;
-  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,
@@ -450,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;
 }
@@ -511,11 +565,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
@@ -528,8 +582,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;
   }
 
@@ -543,7 +597,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));
@@ -551,10 +605,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
@@ -562,7 +616,7 @@
 #endif
       hdr_white_nits = kHlgMaxNits;
       break;
-    case JPEGR_TF_PQ:
+    case ULTRAHDR_TF_PQ:
 #if USE_PQ_INVOETF_LUT
       hdrInvOetf = pqInvOetfLUT;
 #else
@@ -577,22 +631,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;
   }
@@ -604,7 +660,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;
@@ -629,7 +686,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);
         }
       }
     }
@@ -655,13 +712,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;
@@ -670,13 +728,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;
 
@@ -691,33 +749,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;
@@ -729,7 +788,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;
@@ -767,9 +826,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;
   }
@@ -807,23 +866,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:
@@ -852,20 +911,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;
@@ -882,9 +941,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();
 
@@ -946,7 +1005,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));
@@ -966,7 +1025,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;
@@ -980,25 +1039,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);
@@ -1011,4 +1088,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 94%
rename from libs/jpegrecoverymap/tests/Android.bp
rename to libs/ultrahdr/tests/Android.bp
index d5da7fb..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,7 +38,7 @@
         "libgtest",
         "libjpegdecoder",
         "libjpegencoder",
-        "libjpegrecoverymap",
+        "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/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/FileBlobCache.cpp b/opengl/libs/EGL/FileBlobCache.cpp
index 3f7ae7e..1026842 100644
--- a/opengl/libs/EGL/FileBlobCache.cpp
+++ b/opengl/libs/EGL/FileBlobCache.cpp
@@ -31,7 +31,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++) {
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..415e8ea 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>
@@ -236,29 +237,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 +266,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);
     }
 
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..c2c856e 100644
--- a/opengl/libs/EGL/egl_display.cpp
+++ b/opengl/libs/EGL/egl_display.cpp
@@ -322,6 +322,16 @@
 
         mExtensionString = gBuiltinExtensionString;
 
+        // b/269060366 Conditionally enabled EGL_ANDROID_get_frame_timestamps extension if the
+        // device's present timestamps are reliable (which may not be the case on emulators).
+        if (cnx->useAngle) {
+            if (android::base::GetBoolProperty("service.sf.present_timestamp", false)) {
+                mExtensionString.append("EGL_ANDROID_get_frame_timestamps");
+            }
+        } else {
+            mExtensionString.append("EGL_ANDROID_get_frame_timestamps");
+        }
+
         hasColorSpaceSupport = findExtension(disp.queryString.extensions, "EGL_KHR_gl_colorspace");
 
         // Note: CDD requires that devices supporting wide color and/or HDR color also support
@@ -361,6 +371,7 @@
                     findExtension(disp.queryString.extensions, "EGL_KHR_image_gl_colorspace")) {
                     mExtensionString.append("EGL_EXT_image_gl_colorspace ");
                 }
+
                 if (findExtension(disp.queryString.extensions, ext.c_str(), len)) {
                     mExtensionString.append(ext + " ");
                 }
diff --git a/opengl/libs/EGL/egl_platform_entries.cpp b/opengl/libs/EGL/egl_platform_entries.cpp
index 0527c8a..2bca14d 100644
--- a/opengl/libs/EGL/egl_platform_entries.cpp
+++ b/opengl/libs/EGL/egl_platform_entries.cpp
@@ -84,7 +84,8 @@
 // Extensions implemented by the EGL wrapper.
 const char* const gBuiltinExtensionString =
         "EGL_ANDROID_front_buffer_auto_refresh "
-        "EGL_ANDROID_get_frame_timestamps "
+        // b/269060366 Conditionally enabled during display initialization:
+        //"EGL_ANDROID_get_frame_timestamps "
         "EGL_ANDROID_get_native_client_buffer "
         "EGL_ANDROID_presentation_time "
         "EGL_EXT_surface_CTA861_3_metadata "
@@ -2185,6 +2186,10 @@
         return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
     }
 
+    if (!dp->haveExtension("EGL_ANDROID_get_frame_timestamps")) {
+        return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
+    }
+
     SurfaceRef _s(dp, surface);
     if (!_s.get()) {
         return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE);
@@ -2218,6 +2223,10 @@
         return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
     }
 
+    if (!dp->haveExtension("EGL_ANDROID_get_frame_timestamps")) {
+        return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
+    }
+
     SurfaceRef _s(dp, surface);
     if (!_s.get()) {
         return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE);
@@ -2272,6 +2281,10 @@
         return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
     }
 
+    if (!dp->haveExtension("EGL_ANDROID_get_frame_timestamps")) {
+        return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
+    }
+
     SurfaceRef _s(dp, surface);
     if (!_s.get()) {
         return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE);
@@ -2302,6 +2315,10 @@
         return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
     }
 
+    if (!dp->haveExtension("EGL_ANDROID_get_frame_timestamps")) {
+        return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
+    }
+
     SurfaceRef _s(dp, surface);
     if (!_s.get()) {
         return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE);
@@ -2387,6 +2404,10 @@
         return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
     }
 
+    if (!dp->haveExtension("EGL_ANDROID_get_frame_timestamps")) {
+        return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
+    }
+
     SurfaceRef _s(dp, surface);
     if (!_s.get()) {
         return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE);
diff --git a/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/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..e04481c 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",
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..472d7a1 100644
--- a/services/inputflinger/InputManager.cpp
+++ b/services/inputflinger/InputManager.cpp
@@ -110,10 +110,6 @@
     return *mReader;
 }
 
-UnwantedInteractionBlockerInterface& InputManager::getBlocker() {
-    return *mBlocker;
-}
-
 InputProcessorInterface& InputManager::getProcessor() {
     return *mProcessor;
 }
@@ -129,6 +125,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..793757d 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 {
@@ -108,10 +108,10 @@
     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..f852001 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -207,7 +207,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 +235,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);
@@ -277,12 +277,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();
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 cd427f0..a6d1810 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>
@@ -116,11 +117,7 @@
     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>";
     }
@@ -625,7 +622,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);
@@ -1298,7 +1297,7 @@
             (keyEntry.policyFlags & POLICY_FLAG_PASS_TO_USER);
 }
 
-bool InputDispatcher::isAppSwitchPendingLocked() {
+bool InputDispatcher::isAppSwitchPendingLocked() const {
     return mAppSwitchDueTime != LLONG_MAX;
 }
 
@@ -1862,11 +1861,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),
@@ -2905,7 +2905,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 {
@@ -3619,8 +3619,8 @@
                                                      const sp<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.
@@ -4025,9 +4025,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;
@@ -4035,7 +4035,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
 
@@ -4083,23 +4083,22 @@
     }
 }
 
-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);
+    if (!validateKeyEvent(args.action)) {
         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;
@@ -4113,13 +4112,13 @@
 
     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);
@@ -4144,10 +4143,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();
@@ -4158,50 +4156,50 @@
     }
 }
 
-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();
+    if (!validateMotionEvent(args.action, args.actionButton, args.pointerCount,
+                             args.pointerProperties)) {
+        LOG(ERROR) << "Invalid event: " << args.dump();
         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());
@@ -4213,10 +4211,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;
                 }
             }
@@ -4224,20 +4222,20 @@
 
         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)) {
@@ -4249,21 +4247,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));
@@ -4275,12 +4272,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;
@@ -4289,10 +4286,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();
@@ -4303,34 +4299,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;
@@ -4338,7 +4334,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
 
@@ -4347,17 +4343,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
 
@@ -4372,10 +4368,10 @@
                                                             std::chrono::milliseconds timeout,
                                                             uint32_t policyFlags) {
     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();
 
@@ -4394,7 +4390,7 @@
 
     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)) {
@@ -4440,7 +4436,7 @@
             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 =
@@ -4516,7 +4512,7 @@
         }
 
         default:
-            ALOGW("Cannot inject %s events", inputEventTypeToString(event->getType()));
+            LOG(WARNING) << "Cannot inject " << ftl::enum_string(event->getType()) << " events";
             return InputEventInjectionResult::FAILED;
     }
 
@@ -4606,14 +4602,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);
@@ -4629,7 +4625,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;
@@ -5415,7 +5411,7 @@
     mReplacedKeys.clear();
 }
 
-void InputDispatcher::logDispatchStateLocked() {
+void InputDispatcher::logDispatchStateLocked() const {
     std::string dump;
     dumpDispatchStateLocked(dump);
 
@@ -5427,7 +5423,7 @@
     }
 }
 
-std::string InputDispatcher::dumpPointerCaptureStateLocked() {
+std::string InputDispatcher::dumpPointerCaptureStateLocked() const {
     std::string dump;
 
     dump += StringPrintf(INDENT "Pointer Capture Requested: %s\n",
@@ -5445,7 +5441,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));
@@ -5515,14 +5511,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);
                 }
@@ -5548,7 +5544,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));
@@ -5571,7 +5567,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));
@@ -5653,7 +5649,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];
@@ -6197,7 +6193,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);
     }
@@ -6206,7 +6202,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.  "
@@ -6225,12 +6221,12 @@
             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);
@@ -6240,7 +6236,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.  "
@@ -6275,53 +6271,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());
@@ -6335,7 +6331,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();
@@ -6345,7 +6341,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 {
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 2246d47..76b15e2 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -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,
@@ -231,7 +232,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
@@ -331,8 +332,8 @@
             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);
@@ -646,10 +647,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);
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/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/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index 3d3a8ea..0eb4ad2 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;
@@ -1530,6 +1536,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);
@@ -2281,8 +2301,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") {
@@ -2535,6 +2555,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 69363b6..b86906b 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -146,98 +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/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(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))});
@@ -253,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);
@@ -275,12 +201,14 @@
     mIsExternal = mClasses.test(InputDeviceClass::EXTERNAL);
     mHasMic = mClasses.test(InputDeviceClass::MIC);
 
+    using Change = InputReaderConfiguration::Change;
+
     if (!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) {
                 std::optional<PropertyMap> configuration =
@@ -291,10 +219,10 @@
             });
 
             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);
@@ -311,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) {
@@ -321,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;
@@ -338,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;
@@ -356,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.",
@@ -367,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",
@@ -376,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.
@@ -384,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);
         }
     }
@@ -465,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);
@@ -507,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(std::make_unique<SwitchInputMapper>(contextPtr, readerConfig));
+    }
+
+    // Scroll wheel-like devices.
+    if (classes.test(InputDeviceClass::ROTARY_ENCODER)) {
+        mappers.push_back(std::make_unique<RotaryEncoderInputMapper>(contextPtr, readerConfig));
+    }
+
+    // Vibrator-like devices.
+    if (classes.test(InputDeviceClass::VIBRATOR)) {
+        mappers.push_back(std::make_unique<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(std::make_unique<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(std::make_unique<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(std::make_unique<JoystickInputMapper>(contextPtr, readerConfig));
+    }
+
+    // Motion sensor enabled devices.
+    if (classes.test(InputDeviceClass::SENSOR)) {
+        mappers.push_back(std::make_unique<SensorInputMapper>(contextPtr, readerConfig));
+    }
+
+    // External stylus-like devices.
+    if (classes.test(InputDeviceClass::EXTERNAL_STYLUS)) {
+        mappers.push_back(std::make_unique<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 0b15efe..20612c7 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -388,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>
@@ -567,6 +571,8 @@
 
     status_t disableDevice(int32_t deviceId) override final;
 
+    void sysfsNodeChanged(const std::string& sysfsNodePath) override final;
+
     ~EventHub() override;
 
 private:
@@ -578,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 4ae06fe..6303546 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];
@@ -152,7 +153,7 @@
     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 +192,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
@@ -403,7 +407,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();
     }
@@ -420,7 +424,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/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index 13e4d0c..8ef5ff6 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,12 @@
 
 // --- 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()) {
+    configureWithZeroChanges(readerConfig);
+}
 
 CursorInputMapper::~CursorInputMapper() {
     if (mPointerController != nullptr) {
@@ -79,30 +85,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,138 +137,46 @@
     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()) {
+        configureWithZeroChanges(readerConfig);
+        return out;
     }
 
     const bool configurePointerCapture = mParameters.mode != Parameters::Mode::NAVIGATION &&
-            ((!changes && config->pointerCaptureRequest.enable) ||
-             (changes & InputReaderConfiguration::CHANGE_POINTER_CAPTURE));
+            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) ||
-        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);
-        }
+    if (changes.test(InputReaderConfiguration::Change::POINTER_SPEED) || configurePointerCapture) {
+        configureOnChangePointerSpeed(readerConfig);
     }
 
-    if (!changes || (changes & 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();
+    if (changes.test(InputReaderConfiguration::Change::DISPLAY_INFO) || configurePointerCapture) {
+        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") {
+    const PropertyMap& config = getDeviceContext().getConfiguration();
+    std::optional<std::string> cursorModeString = config.getString("cursor.mode");
+    if (cursorModeString.has_value()) {
+        if (*cursorModeString == "navigation") {
             mParameters.mode = Parameters::Mode::NAVIGATION;
-        } else if (cursorModeString != "pointer" && cursorModeString != "default") {
-            ALOGW("Invalid value for cursor.mode: '%s'", cursorModeString.c_str());
+        } else if (*cursorModeString != "pointer" && *cursorModeString != "default") {
+            ALOGW("Invalid value for cursor.mode: '%s'", cursorModeString->c_str());
         }
     }
 
-    mParameters.orientationAware = false;
-    getDeviceContext().getConfiguration().tryGetProperty("cursor.orientationAware",
-                                                         mParameters.orientationAware);
+    mParameters.orientationAware = config.getBool("cursor.orientationAware").value_or(false);
 
     mParameters.hasAssociatedDisplay = false;
     if (mParameters.mode == Parameters::Mode::POINTER || mParameters.orientationAware) {
@@ -348,7 +263,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 +286,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 +424,117 @@
     return mDisplayId;
 }
 
+void CursorInputMapper::configureWithZeroChanges(const InputReaderConfiguration& readerConfig) {
+    // Configuration with zero changes
+    configureBasicParams();
+    if (mParameters.mode != Parameters::Mode::NAVIGATION &&
+        readerConfig.pointerCaptureRequest.enable) {
+        configureOnPointerCapture(readerConfig);
+    }
+    configureOnChangePointerSpeed(readerConfig);
+    configureOnChangeDisplayInfo(readerConfig);
+}
+
+void CursorInputMapper::configureBasicParams() {
+    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;
+}
+
+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..caf2e5a 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -53,15 +53,16 @@
 
 class CursorInputMapper : public InputMapper {
 public:
-    explicit CursorInputMapper(InputDeviceContext& deviceContext);
+    explicit CursorInputMapper(InputDeviceContext& deviceContext,
+                               const InputReaderConfiguration& readerConfig);
     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;
 
@@ -126,6 +127,11 @@
 
     void configureParameters();
     void dumpParameters(std::string& dump);
+    void configureWithZeroChanges(const InputReaderConfiguration& readerConfig);
+    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);
 };
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..841c437 100644
--- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h
+++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h
@@ -26,15 +26,16 @@
 
 class ExternalStylusInputMapper : public InputMapper {
 public:
-    explicit ExternalStylusInputMapper(InputDeviceContext& deviceContext);
+    explicit ExternalStylusInputMapper(InputDeviceContext& deviceContext,
+                                       const InputReaderConfiguration& readerConfig);
     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;
 
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..f017317 100644
--- a/services/inputflinger/reader/mapper/InputMapper.h
+++ b/services/inputflinger/reader/mapper/InputMapper.h
@@ -31,16 +31,16 @@
  * 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);
+    explicit InputMapper(InputDeviceContext& deviceContext,
+                         const InputReaderConfiguration& readerConfig);
     virtual ~InputMapper();
 
     inline int32_t getDeviceId() { return mDeviceContext.getId(); }
@@ -51,11 +51,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);
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..49673a2 100644
--- a/services/inputflinger/reader/mapper/JoystickInputMapper.h
+++ b/services/inputflinger/reader/mapper/JoystickInputMapper.h
@@ -22,15 +22,16 @@
 
 class JoystickInputMapper : public InputMapper {
 public:
-    explicit JoystickInputMapper(InputDeviceContext& deviceContext);
+    explicit JoystickInputMapper(InputDeviceContext& deviceContext,
+                                 const InputReaderConfiguration& readerConfig);
     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;
 
@@ -106,7 +107,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 dc0454d..582fb46 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -63,9 +63,10 @@
 
 // --- 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;
@@ -85,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));
         }
     }
@@ -118,51 +119,47 @@
 }
 
 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)) {
+    if (!changes.any() ||
+        changes.test(InputReaderConfiguration::Change::KEYBOARD_LAYOUT_ASSOCIATION)) {
         mKeyboardLayoutInfo =
-                getValueByKey(config->keyboardLayoutAssociations, getDeviceContext().getLocation());
+                getValueByKey(config.keyboardLayoutAssociations, getDeviceContext().getLocation());
     }
 
     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 {
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..a617420 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
@@ -23,7 +23,8 @@
 
 class MultiTouchInputMapper : public TouchInputMapper {
 public:
-    explicit MultiTouchInputMapper(InputDeviceContext& deviceContext);
+    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..d3dcbe1 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
@@ -25,15 +25,16 @@
 
 class RotaryEncoderInputMapper : public InputMapper {
 public:
-    explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext);
+    explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext,
+                                      const InputReaderConfiguration& readerConfig);
     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;
 
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
index 3d60bfd..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..1f82559 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,16 @@
 
 class SensorInputMapper : public InputMapper {
 public:
-    explicit SensorInputMapper(InputDeviceContext& deviceContext);
+    explicit SensorInputMapper(InputDeviceContext& deviceContext,
+                               const InputReaderConfiguration& readerConfig);
     ~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,
@@ -120,9 +124,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..9341007 100644
--- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h
@@ -23,7 +23,8 @@
 
 class SingleTouchInputMapper : public TouchInputMapper {
 public:
-    explicit SingleTouchInputMapper(InputDeviceContext& deviceContext);
+    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..7ec282b 100644
--- a/services/inputflinger/reader/mapper/SwitchInputMapper.h
+++ b/services/inputflinger/reader/mapper/SwitchInputMapper.h
@@ -22,7 +22,8 @@
 
 class SwitchInputMapper : public InputMapper {
 public:
-    explicit SwitchInputMapper(InputDeviceContext& deviceContext);
+    explicit SwitchInputMapper(InputDeviceContext& deviceContext,
+                               const InputReaderConfiguration& readerConfig);
     virtual ~SwitchInputMapper();
 
     virtual uint32_t getSources() const override;
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 96c163d..c72a263 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -121,12 +121,12 @@
 
 // --- 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),
+        mParameters(computeParameters(deviceContext)) {}
 
 TouchInputMapper::~TouchInputMapper() {}
 
@@ -134,16 +134,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 +153,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 +229,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 +249,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 +261,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 +287,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 +313,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 +360,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) {
@@ -876,14 +881,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.
@@ -1160,92 +1177,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() {
@@ -1599,10 +1603,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);
             }
         }
@@ -1675,7 +1679,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 |
@@ -1722,7 +1725,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;
@@ -1860,6 +1863,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.
@@ -1870,7 +1894,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) {
@@ -2691,13 +2715,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();
@@ -2898,8 +2921,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();
@@ -2907,7 +2929,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);
@@ -2925,8 +2947,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");
@@ -2943,8 +2964,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);
@@ -2988,8 +3008,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;
@@ -3025,15 +3044,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);
@@ -3051,8 +3069,6 @@
         prepareMultiFingerPointerGestures(when, outCancelPreviousGesture, outFinishPreviousGesture);
     }
 
-    mPointerController->setButtonState(mCurrentRawState.buttonState);
-
     if (DEBUG_GESTURES) {
         ALOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, "
               "currentGestureMode=%d, currentGestureIdBits=0x%08x, "
@@ -3065,9 +3081,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));
         }
@@ -3076,9 +3093,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));
         }
@@ -3193,8 +3211,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.
@@ -3367,7 +3385,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);
@@ -3460,7 +3478,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 +
@@ -3502,15 +3520,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);
@@ -3523,7 +3545,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,
@@ -3548,9 +3570,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);
@@ -3567,7 +3588,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,
@@ -3581,24 +3603,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;
@@ -3609,8 +3630,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 */ {}));
     }
 
@@ -3618,15 +3640,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) {
@@ -3642,7 +3664,7 @@
                                            AMOTION_EVENT_EDGE_FLAG_NONE, 1,
                                            &mPointerSimple.currentProperties,
                                            &mPointerSimple.currentCoords, mOrientedXPrecision,
-                                           mOrientedYPrecision, xCursorPosition, yCursorPosition,
+                                           mOrientedYPrecision, cursorPosition.x, cursorPosition.y,
                                            mPointerSimple.downTime, /* videoFrames */ {}));
         }
 
@@ -3653,7 +3675,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 */ {}));
     }
 
@@ -3669,7 +3691,7 @@
                                            AMOTION_EVENT_EDGE_FLAG_NONE, 1,
                                            &mPointerSimple.currentProperties,
                                            &mPointerSimple.currentCoords, mOrientedXPrecision,
-                                           mOrientedYPrecision, xCursorPosition, yCursorPosition,
+                                           mOrientedYPrecision, cursorPosition.x, cursorPosition.y,
                                            mPointerSimple.downTime, /* videoFrames */ {}));
         }
 
@@ -3680,8 +3702,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) {
@@ -3701,8 +3723,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 */ {}));
     }
 
@@ -3712,8 +3734,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();
     }
@@ -3819,7 +3841,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..7141924 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,16 @@
 
 class TouchInputMapper : public InputMapper {
 public:
-    explicit TouchInputMapper(InputDeviceContext& deviceContext);
+    explicit TouchInputMapper(InputDeviceContext& deviceContext,
+                              const InputReaderConfiguration& readerConfig);
     ~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 +192,7 @@
     };
 
     // Input sources and device mode.
-    uint32_t mSource;
+    uint32_t mSource{0};
 
     enum class DeviceMode {
         DISABLED,   // input is disabled
@@ -199,7 +203,7 @@
 
         ftl_last = POINTER
     };
-    DeviceMode mDeviceMode;
+    DeviceMode mDeviceMode{DeviceMode::DISABLED};
 
     // The reader's configuration.
     InputReaderConfiguration mConfig;
@@ -373,7 +377,6 @@
 
     std::vector<VirtualKey> mVirtualKeys;
 
-    virtual void configureParameters();
     virtual void dumpParameters(std::string& dump);
     virtual void configureRawPointerAxes();
     virtual void dumpRawPointerAxes(std::string& dump);
@@ -409,7 +412,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.
@@ -786,7 +789,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 +821,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 3309767..8753b48 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -57,7 +57,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) {
@@ -169,8 +169,9 @@
 
 } // 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),
@@ -206,6 +207,11 @@
     return AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD;
 }
 
+void TouchpadInputMapper::populateDeviceInfo(InputDeviceInfo& info) {
+    InputMapper::populateDeviceInfo(info);
+    mGestureConverter.populateMotionRanges(info);
+}
+
 void TouchpadInputMapper::dump(std::string& dump) {
     dump += INDENT2 "Touchpad Input Mapper:\n";
     dump += INDENT3 "Gesture converter:\n";
@@ -214,45 +220,46 @@
     dump += addLinePrefix(mPropertyProvider.dump(), INDENT4);
 }
 
-std::list<NotifyArgs> TouchpadInputMapper::configure(nsecs_t when,
-                                                     const InputReaderConfiguration* config,
-                                                     uint32_t changes) {
-    if (!changes) {
+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 || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
+    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("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> TouchpadInputMapper::reset(nsecs_t when) {
     mStateConverter.reset();
-    mGestureConverter.reset();
-    return InputMapper::reset(when);
+    std::list<NotifyArgs> out = mGestureConverter.reset(when);
+    out += InputMapper::reset(when);
+    return out;
 }
 
 std::list<NotifyArgs> TouchpadInputMapper::process(const RawEvent* rawEvent) {
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
index d693bca..268b275 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -37,15 +37,17 @@
 
 class TouchpadInputMapper : public InputMapper {
 public:
-    explicit TouchpadInputMapper(InputDeviceContext& deviceContext);
+    explicit TouchpadInputMapper(InputDeviceContext& deviceContext,
+                                 const InputReaderConfiguration& readerConfig);
     ~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;
 
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..384c075 100644
--- a/services/inputflinger/reader/mapper/VibratorInputMapper.h
+++ b/services/inputflinger/reader/mapper/VibratorInputMapper.h
@@ -22,11 +22,12 @@
 
 class VibratorInputMapper : public InputMapper {
 public:
-    explicit VibratorInputMapper(InputDeviceContext& deviceContext);
+    explicit VibratorInputMapper(InputDeviceContext& deviceContext,
+                                 const InputReaderConfiguration& readerConfig);
     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,
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..fd2be5f 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,
@@ -98,7 +145,6 @@
         case kGestureTypePinch:
             return handlePinch(when, readTime, gesture);
         default:
-            // TODO(b/251196347): handle more gesture types.
             return {};
     }
 }
@@ -111,8 +157,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 +182,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 +243,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,9 +303,12 @@
     // TODO(b/262876643): set AXIS_GESTURE_{X,Y}_OFFSET.
     coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, -gesture.details.scroll.dx);
     coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, -gesture.details.scroll.dy);
-    out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
-                                 mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
-                                 mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
+    NotifyMotionArgs args =
+            makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
+                           mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
+                           mFakeFingerCoords.data(), xCursorPosition, yCursorPosition);
+    args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
+    out.push_back(args);
     return out;
 }
 
@@ -239,14 +321,18 @@
         return {};
     }
 
-    float xCursorPosition, yCursorPosition;
-    mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+    return endScroll(when, readTime);
+}
+
+NotifyArgs 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 +342,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 +378,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 +388,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 +404,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 +427,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 +450,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 +465,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 +477,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,10 +507,7 @@
                                                   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,
+    return NotifyMotionArgs(mReaderContext.getNextId(), when, readTime, mDeviceId, SOURCE,
                             mPointerController->getDisplayId(), /* policyFlags= */ POLICY_FLAG_WAKE,
                             action, /* actionButton= */ actionButton, /* flags= */ 0,
                             mReaderContext.getGlobalMetaState(), buttonState,
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
index 2ec5841..70e8fb7 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,7 +44,9 @@
     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);
@@ -52,15 +55,19 @@
     [[nodiscard]] NotifyArgs 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]] NotifyArgs 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 +99,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..8841b6e 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>
@@ -78,16 +81,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 +104,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..c314b0d 100644
--- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
@@ -54,7 +54,6 @@
     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 3d88338..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>
@@ -255,36 +256,34 @@
     bool parsedSuccessfully = false;
     Visitor setVisitor{
             [&](int*) {
-                int32_t value;
-                parsedSuccessfully = idcProperties.tryGetProperty(propertyName, value);
-                if (parsedSuccessfully) {
-                    setIntValues({value});
+                if (std::optional<int32_t> value = idcProperties.getInt(propertyName); value) {
+                    parsedSuccessfully = true;
+                    setIntValues({*value});
                 }
             },
             [&](GesturesPropBool*) {
-                bool value;
-                parsedSuccessfully = idcProperties.tryGetProperty(propertyName, value);
-                if (parsedSuccessfully) {
-                    setBoolValues({value});
+                if (std::optional<bool> value = idcProperties.getBool(propertyName); value) {
+                    parsedSuccessfully = true;
+                    setBoolValues({*value});
                 }
             },
             [&](double*) {
-                double value;
-                parsedSuccessfully = idcProperties.tryGetProperty(propertyName, value);
-                if (parsedSuccessfully) {
-                    setRealValues({value});
+                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\" could set due to a type mismatch.",
+    ALOGE_IF(!parsedSuccessfully, "Gesture property \"%s\" couldn't be set due to a type mismatch.",
              mName.c_str());
-    return;
 }
 
 template <typename T, typename U>
diff --git a/services/inputflinger/tests/FakeEventHub.cpp b/services/inputflinger/tests/FakeEventHub.cpp
index ff6d584..4626f5a 100644
--- a/services/inputflinger/tests/FakeEventHub.cpp
+++ b/services/inputflinger/tests/FakeEventHub.cpp
@@ -594,4 +594,33 @@
     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)) {
+        removeDevice(foundDeviceId);
+        addDevice(foundDeviceId, foundDevice->identifier.name,
+                  foundDevice->classes | InputDeviceClass::LIGHT, foundDevice->identifier.bus);
+    }
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/FakeEventHub.h b/services/inputflinger/tests/FakeEventHub.h
index e0a3f9e..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;
@@ -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..c6d541e 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,107 @@
                       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)));
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/HardwareStateConverter_test.cpp b/services/inputflinger/tests/HardwareStateConverter_test.cpp
index 36b9bab..19d46c8 100644
--- a/services/inputflinger/tests/HardwareStateConverter_test.cpp
+++ b/services/inputflinger/tests/HardwareStateConverter_test.cpp
@@ -13,11 +13,14 @@
  * 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"
@@ -28,38 +31,37 @@
 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) {
+        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_SLOT, 0, 7, 0, 0, 0);
+        mConverter = std::make_unique<HardwareStateConverter>(mDeviceContext);
+    }
+
 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 +69,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 +81,37 @@
         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;
+    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 +139,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 +189,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 +249,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 e299643..27d7b9c 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -213,7 +213,7 @@
 
     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,7 +224,7 @@
 
     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);
@@ -530,17 +530,21 @@
     bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) override {
         std::scoped_lock lock(mLock);
         switch (inputEvent->getType()) {
-            case AINPUT_EVENT_TYPE_KEY: {
+            case InputEventType::KEY: {
                 const KeyEvent* keyEvent = static_cast<const KeyEvent*>(inputEvent);
                 mFilteredEvent = std::make_unique<KeyEvent>(*keyEvent);
                 break;
             }
 
-            case AINPUT_EVENT_TYPE_MOTION: {
+            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;
     }
@@ -637,14 +641,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 +818,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 +827,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 +927,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 +935,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 +951,7 @@
                 }
                 break;
             }
-            case AINPUT_EVENT_TYPE_MOTION: {
+            case InputEventType::MOTION: {
                 const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event);
                 assertMotionAction(expectedAction, motionEvent.getAction());
 
@@ -961,21 +960,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 +983,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 +1000,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 +1014,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 +1028,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 +1043,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 +1057,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");
@@ -1243,12 +1233,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 +1259,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 +1268,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 +1294,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 +1319,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 +1368,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 +1452,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;
@@ -1722,7 +1710,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 +1742,9 @@
     return args;
 }
 
-static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source, int32_t displayId,
-                                           const std::vector<PointF>& points) {
+[[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 +1756,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 +1946,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 +2052,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 +2068,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 +2085,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 +2137,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 +2162,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 +2250,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 +2335,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 +2349,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 +2363,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 +2378,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 +2392,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 +2406,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()));
@@ -2464,53 +2441,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 +2517,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 +2587,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 +2596,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 +2640,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 +2652,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 +2662,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 +2676,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 +2687,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 +2723,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 +2735,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 +2748,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 +2761,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 +2773,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 +2801,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 +2861,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 +2893,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 +2912,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 +2953,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 +2982,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 +3010,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 +3022,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 +3032,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 +3046,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 +3075,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 +3084,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 +3094,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 +3105,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 +3115,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 +3123,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 +3132,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 +3156,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 +3220,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 +3254,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 +3264,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 +3283,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 +3307,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 +3324,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 +3339,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 +3368,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 +3376,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 +3387,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 +3397,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,9 +3405,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()));
     window->consumeMotionUp(ADISPLAY_ID_DEFAULT);
 
@@ -3496,9 +3413,7 @@
               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 +3436,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 +3460,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 +3499,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 +3516,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 +3544,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 +3565,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 +3609,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,18 +3628,15 @@
 
     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)));
 }
@@ -3746,11 +3652,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 +3674,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 +3705,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 +3738,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 +3757,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();
@@ -3874,10 +3776,10 @@
 
     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);
@@ -3887,8 +3789,8 @@
 
     window->consumeFocusEvent(false);
 
-    mDispatcher->notifyKey(&keyDown);
-    mDispatcher->notifyKey(&keyUp);
+    mDispatcher->notifyKey(keyDown);
+    mDispatcher->notifyKey(keyUp);
     window->assertNoEvents();
 }
 
@@ -3911,8 +3813,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,
@@ -3996,10 +3898,9 @@
     // 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 performed with the point and the bounds being in
     // different coordinate spaces, the event would end up in the incorrect window.
-    NotifyMotionArgs downMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT, {PointF{75, 55}});
-    mDispatcher->notifyMotion(&downMotionArgs);
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                 {PointF{75, 55}}));
 
     firstWindow->consumeMotionDown();
     secondWindow->assertNoEvents();
@@ -4033,7 +3934,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();
@@ -4050,10 +3951,9 @@
     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();
@@ -4108,14 +4008,14 @@
     for (const auto pointInsideWindow : insidePoints) {
         const vec2 p = displayTransform.inverse().transform(pointInsideWindow);
         const PointF pointInDisplaySpace{p.x, p.y};
-        const auto down = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                             ADISPLAY_ID_DEFAULT, {pointInDisplaySpace});
-        mDispatcher->notifyMotion(&down);
+        mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                     AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                     {pointInDisplaySpace}));
         window->consumeMotionDown();
 
-        const auto up = generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                           ADISPLAY_ID_DEFAULT, {pointInDisplaySpace});
-        mDispatcher->notifyMotion(&up);
+        mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP,
+                                                     AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                     {pointInDisplaySpace}));
         window->consumeMotionUp();
     }
 
@@ -4125,13 +4025,13 @@
     for (const auto pointOutsideWindow : outsidePoints) {
         const vec2 p = displayTransform.inverse().transform(pointOutsideWindow);
         const PointF pointInDisplaySpace{p.x, p.y};
-        const auto down = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                             ADISPLAY_ID_DEFAULT, {pointInDisplaySpace});
-        mDispatcher->notifyMotion(&down);
+        mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                     AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                     {pointInDisplaySpace}));
 
-        const auto up = generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                           ADISPLAY_ID_DEFAULT, {pointInDisplaySpace});
-        mDispatcher->notifyMotion(&up);
+        mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP,
+                                                     AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                     {pointInDisplaySpace}));
     }
     window->assertNoEvents();
 }
@@ -4169,10 +4069,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();
@@ -4189,10 +4087,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();
@@ -4228,10 +4124,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();
@@ -4246,10 +4140,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();
@@ -4275,19 +4167,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();
@@ -4302,19 +4191,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();
@@ -4345,10 +4230,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();
@@ -4368,10 +4251,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();
@@ -4415,19 +4296,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();
@@ -4439,19 +4318,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();
@@ -4481,19 +4357,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();
@@ -4508,19 +4382,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();
@@ -4655,8 +4526,7 @@
 
     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);
@@ -4669,8 +4539,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();
@@ -4685,13 +4554,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);
@@ -4718,19 +4584,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();
@@ -4740,16 +4604,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();
@@ -4782,8 +4644,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(); }
@@ -4791,17 +4653,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);
     }
 
@@ -4815,7 +4677,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);
     }
 
@@ -4825,8 +4687,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);
@@ -4971,7 +4833,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);
 
@@ -4981,8 +4843,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);
 }
 
@@ -5050,8 +4912,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);
@@ -5092,10 +4954,10 @@
 
     mDispatcher->onWindowInfosChanged({*window->getInfo()}, {displayInfo});
 
-    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);
@@ -5255,7 +5117,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);
 
@@ -5266,7 +5129,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);
@@ -5275,15 +5138,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();
 }
 
@@ -5389,17 +5254,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();
 
@@ -5439,7 +5304,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);
@@ -5450,8 +5315,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();
@@ -5463,10 +5327,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);
     }
 };
@@ -5526,8 +5390,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();
@@ -5639,7 +5502,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.
@@ -5779,10 +5642,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());
@@ -5796,9 +5659,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) {
@@ -5910,7 +5773,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());
     }
@@ -5945,7 +5808,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());
     }
@@ -6076,7 +5939,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))
@@ -6126,9 +5989,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());
@@ -6149,9 +6011,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);
@@ -6825,7 +6686,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());
@@ -6899,7 +6760,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
@@ -7024,17 +6885,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);
@@ -7049,7 +6909,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();
@@ -7106,10 +6966,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.
@@ -7164,10 +7023,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
@@ -7192,10 +7050,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();
@@ -7368,8 +7225,7 @@
     }
 
     void notifyPointerCaptureChanged(const PointerCaptureRequest& request) {
-        const NotifyPointerCaptureChangedArgs args = generatePointerCaptureChangedArgs(request);
-        mDispatcher->notifyPointerCaptureChanged(&args);
+        mDispatcher->notifyPointerCaptureChanged(generatePointerCaptureChangedArgs(request));
     }
 
     PointerCaptureRequest requestAndVerifyPointerCapture(const sp<FakeWindowHandle>& window,
@@ -7545,10 +7401,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));
     }
 };
 
@@ -7937,7 +7792,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()));
@@ -7949,7 +7804,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()));
@@ -8038,8 +7893,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,
@@ -8093,9 +7948,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);
@@ -8107,9 +7960,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);
@@ -8122,9 +7973,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);
@@ -8182,8 +8031,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,
@@ -8209,8 +8058,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,
@@ -8225,8 +8074,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,
@@ -8239,8 +8088,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,
@@ -8265,11 +8114,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}}});
@@ -8311,7 +8158,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()))
@@ -8326,7 +8173,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()))
@@ -8341,7 +8188,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()))
@@ -8366,27 +8213,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();
 }
@@ -8412,27 +8254,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();
 }
@@ -8458,26 +8295,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();
 }
@@ -8813,8 +8645,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,
@@ -8845,8 +8677,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,
@@ -8884,8 +8716,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,
@@ -9007,8 +8839,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,
@@ -9022,9 +8854,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,
@@ -9058,8 +8890,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,
@@ -9073,9 +8905,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,
@@ -9119,8 +8951,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,
@@ -9166,8 +8998,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,
@@ -9211,18 +9043,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..d969034 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.
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 eb58d0b..0b50504 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()));
 }
@@ -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.
@@ -1895,7 +1942,7 @@
     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);
@@ -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, 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))));
 }
 
@@ -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.
@@ -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.
@@ -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);
@@ -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,15 +3680,17 @@
 }
 
 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=*/{});
 
     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,
@@ -3644,7 +3707,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);
@@ -3807,7 +3870,7 @@
     CursorInputMapper& mapper = addMapperAndConfigure<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 +3882,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,
@@ -3837,7 +3900,7 @@
     CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
 
     InputDeviceInfo info;
-    mapper.populateDeviceInfo(&info);
+    mapper.populateDeviceInfo(info);
 
     ASSERT_NO_FATAL_FAILURE(assertMotionRange(info,
             AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_TRACKBALL,
@@ -3874,7 +3937,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 +3955,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 +3976,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 +3994,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);
@@ -4113,7 +4176,6 @@
 
     mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
     mFakePointerController->setPosition(100, 200);
-    mFakePointerController->setButtonState(0);
 
     NotifyMotionArgs motionArgs;
     NotifyKeyArgs keyArgs;
@@ -4124,14 +4186,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 +4200,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 +4222,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 +4244,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 +4258,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 +4265,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 +4285,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 +4299,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 +4322,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 +4336,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 +4359,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 +4373,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 +4396,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 +4410,12 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(0, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(0, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(
             assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
@@ -4400,7 +4430,6 @@
 
     mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
     mFakePointerController->setPosition(100, 200);
-    mFakePointerController->setButtonState(0);
 
     NotifyMotionArgs args;
 
@@ -4427,7 +4456,6 @@
 
     mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
     mFakePointerController->setPosition(100, 200);
-    mFakePointerController->setButtonState(0);
 
     NotifyMotionArgs args;
 
@@ -4485,7 +4513,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));
@@ -4534,7 +4562,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);
@@ -4576,7 +4604,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);
@@ -4602,11 +4630,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);
@@ -4630,11 +4657,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);
@@ -4656,7 +4682,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.
@@ -5231,7 +5257,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 +5281,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 +5304,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);
@@ -5328,7 +5354,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 +5377,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);
@@ -5396,7 +5422,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 +5449,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 +5474,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));
@@ -5491,7 +5517,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 +5542,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 +5565,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);
@@ -5823,7 +5849,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);
@@ -6187,14 +6213,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 +6228,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 +6236,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 +6244,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 +6252,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 +6260,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 +6268,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 +6276,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 +6284,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 +6292,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 +6300,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 +6331,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) {
@@ -6548,7 +6574,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 +6599,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());
@@ -6695,7 +6721,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(
@@ -6719,7 +6745,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());
 }
@@ -6741,12 +6767,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 = addMapperAndConfigure<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 +6877,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) {
@@ -7161,9 +7235,9 @@
 
         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;
     }
@@ -7185,7 +7259,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);
@@ -7220,7 +7294,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);
@@ -7230,7 +7304,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.
@@ -7288,7 +7362,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;
@@ -7331,7 +7405,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);
@@ -7393,7 +7467,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());
@@ -7404,17 +7478,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(
@@ -7425,11 +7499,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(
@@ -7440,13 +7514,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());
@@ -7455,7 +7529,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));
 
@@ -7672,7 +7746,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);
@@ -7691,9 +7765,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],
@@ -7722,9 +7796,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],
@@ -7751,9 +7825,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],
@@ -7774,7 +7848,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);
@@ -7799,7 +7873,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);
@@ -7826,9 +7900,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],
@@ -7855,9 +7929,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],
@@ -7878,7 +7952,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);
@@ -7901,7 +7975,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);
@@ -7989,7 +8063,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));
 
@@ -7997,9 +8071,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],
@@ -8019,9 +8093,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],
@@ -8038,9 +8112,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],
@@ -8050,7 +8124,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));
 
@@ -8065,7 +8139,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));
 
@@ -8083,9 +8157,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],
@@ -8102,9 +8176,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],
@@ -8114,7 +8188,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));
 
@@ -8126,7 +8200,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));
 
@@ -8159,7 +8233,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));
 
@@ -8167,9 +8241,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],
@@ -8187,9 +8261,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],
@@ -8207,9 +8281,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],
@@ -8219,7 +8293,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));
 
@@ -8232,7 +8306,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));
 
@@ -8248,9 +8322,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],
@@ -8268,9 +8342,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],
@@ -8280,7 +8354,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));
 
@@ -8292,7 +8366,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));
 
@@ -8480,7 +8554,7 @@
     MultiTouchInputMapper& mapper = addMapperAndConfigure<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));
@@ -8819,14 +8893,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);
@@ -8834,7 +8908,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);
@@ -8842,7 +8916,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);
@@ -8850,7 +8924,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);
@@ -8858,7 +8932,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);
@@ -8866,7 +8940,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);
@@ -8874,7 +8948,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);
@@ -8882,7 +8956,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);
@@ -8890,7 +8964,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);
@@ -8898,7 +8972,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);
@@ -8906,42 +8980,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
@@ -8952,7 +9026,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) {
@@ -9163,7 +9237,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);
@@ -9220,7 +9293,7 @@
     // 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>();
 
@@ -9240,7 +9313,7 @@
     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>();
 
@@ -9262,7 +9335,7 @@
     ASSERT_TRUE(optionalDisplayViewport.has_value());
     DisplayViewport displayViewport = *optionalDisplayViewport;
 
-    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
     prepareAxes(POSITION);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
 
@@ -9278,7 +9351,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));
@@ -9293,7 +9366,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;
@@ -9331,10 +9404,12 @@
                                             String8("touchScreen"));
 
     // Setup the second touch screen device.
-    MultiTouchInputMapper& mapper2 = device2->addMapper<MultiTouchInputMapper>(SECOND_EVENTHUB_ID);
+    MultiTouchInputMapper& mapper2 =
+            device2->addMapper<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.
@@ -9355,8 +9430,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;
@@ -9387,7 +9462,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());
 }
@@ -9568,7 +9643,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);
@@ -9576,14 +9651,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);
@@ -9591,7 +9666,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);
 }
 
 /**
@@ -9613,7 +9688,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);
@@ -9639,7 +9714,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);
 }
 
 /**
@@ -9661,7 +9736,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);
@@ -9670,7 +9745,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.
@@ -9736,7 +9811,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);
@@ -9745,7 +9820,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.
@@ -9780,7 +9855,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
@@ -9834,7 +9909,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);
@@ -9843,7 +9918,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.
@@ -10040,7 +10115,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.
@@ -10062,7 +10137,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) {
@@ -10085,7 +10160,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(
@@ -10112,7 +10187,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());
 }
@@ -10158,7 +10233,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);
@@ -10298,7 +10372,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());
 }
@@ -10308,7 +10382,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);
@@ -10384,7 +10457,7 @@
 
     // 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());
 }
 
@@ -10448,7 +10521,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);
@@ -10508,7 +10580,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));
@@ -10530,7 +10602,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,
@@ -10568,7 +10640,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));
@@ -10590,7 +10662,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,
@@ -10624,7 +10696,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(
@@ -10650,15 +10722,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.
@@ -10688,7 +10760,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,
@@ -10758,12 +10830,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
@@ -10771,16 +10843,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());
 }
 
@@ -10879,14 +10951,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/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..338b747 100644
--- a/services/inputflinger/tests/TestInputListenerMatchers.h
+++ b/services/inputflinger/tests/TestInputListenerMatchers.h
@@ -138,14 +138,14 @@
 
 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_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..84ac0fd 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);
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 546121d..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;
@@ -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..b0fbe5d 100644
--- a/services/sensorservice/BatteryService.cpp
+++ b/services/sensorservice/BatteryService.cpp
@@ -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/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/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/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..d64231f 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;
@@ -1575,9 +1574,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/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 064bbd2..20f4de1 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -182,11 +182,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);
 }
@@ -523,8 +535,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;
     }
 
@@ -535,7 +547,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..51876e7 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -175,6 +175,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);
@@ -276,7 +277,8 @@
     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..f28bfd4 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -1547,6 +1547,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 +1558,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/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/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/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/LayerCreationArgs.cpp b/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp
index ce21233..cfa2b03 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 = 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) {
@@ -74,4 +73,23 @@
     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 011250c..c26edb5 100644
--- a/services/surfaceflinger/FrontEnd/LayerCreationArgs.h
+++ b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h
@@ -36,6 +36,7 @@
 
 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);
 
@@ -44,6 +45,7 @@
                       bool internalLayer = false);
     LayerCreationArgs(std::optional<uint32_t> id, bool internalLayer = false);
     LayerCreationArgs() = default; // for tracing
+    std::string getDebugString() const;
 
     android::SurfaceFlinger* flinger;
     sp<android::Client> client;
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/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
index 3706225..6cacfb5 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
@@ -41,7 +41,7 @@
             LOG_ALWAYS_FATAL("Duplicate layer id %d found. Existing layer: %s", layer.id,
                              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) {
@@ -164,7 +164,8 @@
     }
 }
 
-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;
@@ -176,7 +177,8 @@
 
             RequestedLayerState* layer = getLayerFromId(layerId);
             if (layer == nullptr) {
-                LOG_ALWAYS_FATAL("%s Layer with layerid=%d not found", __func__, layerId);
+                LOG_ALWAYS_FATAL_IF(!ignoreUnknownLayers, "%s Layer with layerid=%d not found",
+                                    __func__, layerId);
                 continue;
             }
 
@@ -256,23 +258,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 3d9a74c..f0d2c22 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
@@ -39,7 +39,11 @@
 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>&);
+    // 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.
@@ -104,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 5e7c259..a992584 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
@@ -27,7 +27,6 @@
 LayerSnapshot::LayerSnapshot(const RequestedLayerState& state,
                              const LayerHierarchy::TraversalPath& path)
       : path(path) {
-    static uint32_t sUniqueSequenceId = 0;
     // 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
@@ -35,7 +34,12 @@
     // 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.
-    uniqueSequence = sUniqueSequenceId++;
+    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;
@@ -44,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
@@ -179,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..ce7d37e 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,14 @@
     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;
+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 +701,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 +715,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 +733,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 +752,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 +772,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 +817,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 +834,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 +905,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 +922,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 +1002,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 +1051,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 +1070,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 +1120,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 7b1ff27..148c98e 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
@@ -119,10 +119,14 @@
                                   const LayerSnapshot& parentSnapshot);
     void updateChildState(LayerSnapshot& snapshot, const LayerSnapshot& childSnapshot,
                           const Args& args);
+    void updateTouchableRegionCrop(const Args& args);
 
     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 a5fdaf4..b397b82 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -32,10 +32,6 @@
 using namespace ftl::flag_operators;
 
 namespace {
-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 << "{";
@@ -100,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;
@@ -158,12 +154,15 @@
                     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::eBufferChanged) {
+        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
+        changes |= RequestedLayerState::Changes::Buffer;
+    }
+    if (clientState.what & layer_state_t::eSidebandStreamChanged) {
+        changes |= RequestedLayerState::Changes::SidebandStream;
     }
     if (what & (layer_state_t::eAlphaChanged)) {
         if (oldAlpha == 0 || color.a == 0) {
@@ -323,9 +322,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();
 }
 
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
index 216e95f..f15f023 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
@@ -111,6 +111,8 @@
     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;
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/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 0f2af2f..9c232b1 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();
@@ -302,10 +307,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 +342,7 @@
         return nullptr;
     }
     mGetHandleCalled = true;
+    mHandleAlive = true;
     return sp<LayerHandle>::make(mFlinger, sp<Layer>::fromExisting(this));
 }
 
@@ -639,8 +647,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 +1013,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 +1511,7 @@
         transformHint = ui::Transform::ROT_0;
     }
 
-    setTransformHint(transformHint);
+    setTransformHintLegacy(transformHint);
 }
 
 // ----------------------------------------------------------------------------
@@ -1649,9 +1659,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 +1908,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 +2128,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 +2140,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 +2316,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 +2442,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 +2454,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 +2492,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);
@@ -2865,9 +2865,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);
@@ -3060,7 +3064,13 @@
     }
 
     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;
@@ -3132,7 +3142,6 @@
 }
 
 bool Layer::setDataspace(ui::Dataspace dataspace) {
-    mDrawingState.dataspaceRequested = true;
     if (mDrawingState.dataspace == dataspace) return false;
     mDrawingState.dataspace = dataspace;
     mDrawingState.modified = true;
@@ -3141,11 +3150,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 +3177,7 @@
 }
 
 bool Layer::setSurfaceDamageRegion(const Region& surfaceDamage) {
+    if (mDrawingState.surfaceDamageRegion.hasSameRects(surfaceDamage)) return false;
     mDrawingState.surfaceDamageRegion = surfaceDamage;
     mDrawingState.modified = true;
     setTransactionFlags(eTransactionNeeded);
@@ -3396,11 +3406,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);
@@ -3461,20 +3507,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 +3732,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;
         }
     }
@@ -3960,10 +4032,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 +4039,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 +4070,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..8d7c362 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -137,12 +137,18 @@
         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;
@@ -225,8 +231,8 @@
         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;
     };
@@ -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.
      */
@@ -455,8 +476,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
      */
@@ -519,7 +538,7 @@
         uint64_t mFrameNumber;
 
         bool mFrameLatencyNeeded{false};
-        float mDesiredSdrHdrRatio = 1.f;
+        float mDesiredHdrSdrRatio = 1.f;
     };
 
     BufferInfo mBufferInfo;
@@ -570,6 +589,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 +631,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
@@ -641,13 +662,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 +721,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 +729,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 +873,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 +897,12 @@
         };
     };
     bool hasBuffer() const { return mBufferInfo.mBuffer != nullptr; }
+    void setTransformHint(std::optional<ui::Transform::RotationFlags> transformHint) {
+        mTransformHint = transformHint;
+    }
+
+    // Exposed so SurfaceFlinger can assert that it's held
+    const sp<SurfaceFlinger> mFlinger;
 
 protected:
     // For unit tests
@@ -885,10 +921,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 +963,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,14 +1176,15 @@
     float mBorderWidth;
     half4 mBorderColor;
 
-    void setTransformHint(ui::Transform::RotationFlags);
+    void setTransformHintLegacy(ui::Transform::RotationFlags);
 
     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;
     uint64_t mPreviousReleasedFrameNumber = 0;
@@ -1191,6 +1221,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/LayerProtoHelper.cpp b/services/surfaceflinger/LayerProtoHelper.cpp
index b5ae1a7..3472d20 100644
--- a/services/surfaceflinger/LayerProtoHelper.cpp
+++ b/services/surfaceflinger/LayerProtoHelper.cpp
@@ -332,7 +332,7 @@
     if (mTraceFlags & LayerTracing::TRACE_COMPOSITION) {
         auto it = mLegacyLayers.find(layer.id);
         if (it != mLegacyLayers.end()) {
-            it->second->writeCompositionStateToProto(layerProto);
+            it->second->writeCompositionStateToProto(layerProto, snapshot->outputFilter.layerStack);
         }
     }
 
@@ -392,7 +392,11 @@
 
     layerInfo->set_id(snapshot.uniqueSequence);
     layerInfo->set_original_id(snapshot.sequence);
-    layerInfo->set_name(requestedState.name);
+    if (!snapshot.path.isClone()) {
+        layerInfo->set_name(requestedState.name);
+    } else {
+        layerInfo->set_name(requestedState.name + "(Mirror)");
+    }
     layerInfo->set_type("Layer");
 
     LayerProtoHelper::writeToProto(requestedState.transparentRegion,
diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp
index 03a7f22..1b8ff28 100644
--- a/services/surfaceflinger/LayerRenderArea.cpp
+++ b/services/surfaceflinger/LayerRenderArea.cpp
@@ -39,9 +39,12 @@
 
 LayerRenderArea::LayerRenderArea(SurfaceFlinger& flinger, sp<Layer> layer, const Rect& crop,
                                  ui::Size reqSize, ui::Dataspace reqDataSpace, bool childrenOnly,
-                                 bool allowSecureLayers)
+                                 bool allowSecureLayers, const ui::Transform& layerTransform,
+                                 const Rect& layerBufferSize)
       : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, allowSecureLayers),
         mLayer(std::move(layer)),
+        mLayerTransform(layerTransform),
+        mLayerBufferSize(layerBufferSize),
         mCrop(crop),
         mFlinger(flinger),
         mChildrenOnly(childrenOnly) {}
@@ -60,7 +63,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 +74,7 @@
     using namespace std::string_literals;
 
     if (!mChildrenOnly) {
-        mTransform = mLayer->getTransform().inverse();
+        mTransform = mLayerTransform.inverse();
     }
 
     if (mFlinger.mLayerLifecycleManagerEnabled) {
diff --git a/services/surfaceflinger/LayerRenderArea.h b/services/surfaceflinger/LayerRenderArea.h
index 322dbd1..9bb13b3 100644
--- a/services/surfaceflinger/LayerRenderArea.h
+++ b/services/surfaceflinger/LayerRenderArea.h
@@ -33,7 +33,8 @@
 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);
 
     const ui::Transform& getTransform() const override;
     bool isSecure() const override;
@@ -45,6 +46,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/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp
index 327ca3f..531d277 100644
--- a/services/surfaceflinger/RegionSamplingThread.cpp
+++ b/services/surfaceflinger/RegionSamplingThread.cpp
@@ -347,7 +347,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/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/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/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index eec7c08..f136e9f 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -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 ||
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.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 3423652..1f6d378 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -32,6 +32,7 @@
 #include <ui/GraphicTypes.h>
 #pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
 
+#include <ftl/algorithm.h>
 #include <ftl/fake_guard.h>
 #include <ftl/optional.h>
 #include <scheduler/Features.h>
@@ -436,13 +437,13 @@
 
     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));
+                .or_else(ftl::static_ref<RefreshRateSelectorPtr>([] { return nullptr; }))
+                .value();
     }
 
     std::shared_ptr<const VsyncSchedule> getVsyncScheduleLocked(
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 8ce479a..a6f4630 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -478,10 +478,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 ||
@@ -715,12 +711,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());
@@ -1153,7 +1149,10 @@
         displayIdOpt = getPhysicalDisplayIdLocked(displayToken);
     }
 
-    if (!displayIdOpt) {
+    // TODO (b/277364366): Clients should be updated to pass in the display they
+    // want, rather than us picking an arbitrary one (the pacesetter, in this
+    // case).
+    if (displayToken && !displayIdOpt) {
         ALOGE("%s: Invalid physical display token %p", __func__, displayToken.get());
         return NAME_NOT_FOUND;
     }
@@ -2461,16 +2460,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) {
@@ -2563,6 +2553,7 @@
         if (!dropFrame) {
             refreshArgs.outputs.push_back(display->getCompositionDisplay());
         }
+        display->tracePowerMode();
         displayIds.push_back(display->getId());
     }
     mPowerAdvisor->setDisplays(displayIds);
@@ -2622,8 +2613,21 @@
     const auto prevVsyncTime = mExpectedPresentTime - mScheduler->getVsyncSchedule()->period();
     const auto hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration;
 
-    refreshArgs.earliestPresentTime = prevVsyncTime - hwcMinWorkDuration;
-    refreshArgs.previousPresentFence = mPreviousPresentFences[0].fenceTime;
+    const Period vsyncPeriod = mScheduler->getVsyncSchedule()->period();
+    const bool threeVsyncsAhead = mExpectedPresentTime - frameTime > 2 * vsyncPeriod;
+
+    // We should wait for the earliest present time if HWC doesn't support ExpectedPresentTime,
+    // and the next vsync is not already taken by the previous frame.
+    const bool waitForEarliestPresent =
+            !getHwComposer().getComposer()->isSupported(
+                    Hwc2::Composer::OptionalFeature::ExpectedPresentTime) &&
+            (threeVsyncsAhead ||
+             mPreviousPresentFences[0].fenceTime->getSignalTime() != Fence::SIGNAL_TIME_PENDING);
+
+    if (waitForEarliestPresent) {
+        refreshArgs.earliestPresentTime = prevVsyncTime - hwcMinWorkDuration;
+    }
+
     refreshArgs.scheduledFrameTime = mScheduler->getScheduledFrameTime();
     refreshArgs.expectedPresentTime = mExpectedPresentTime.ns();
     refreshArgs.hasTrustedPresentationListener = mNumTrustedPresentationListeners > 0;
@@ -2641,7 +2645,9 @@
         CompositionResult compositionResult{layerFE->stealCompositionResult()};
         layer->onPreComposition(compositionResult.refreshStartTime);
         for (auto releaseFence : compositionResult.releaseFences) {
-            layer->onLayerDisplayed(releaseFence);
+            Layer* clonedFrom = layer->getClonedFrom().get();
+            auto owningLayer = clonedFrom ? clonedFrom : layer;
+            owningLayer->onLayerDisplayed(releaseFence);
         }
         if (compositionResult.lastClientCompositionFence) {
             layer->setWasClientComposed(compositionResult.lastClientCompositionFence);
@@ -2655,9 +2661,7 @@
         mPowerAdvisor->setSfPresentTiming(TimePoint::fromNs(mPreviousPresentFences[0]
                                                                     .fenceTime->getSignalTime()),
                                           TimePoint::now());
-        if (mPowerHintSessionMode.late) {
-            mPowerAdvisor->sendActualWorkDuration();
-        }
+        mPowerAdvisor->reportActualWorkDuration();
     }
 
     if (mScheduler->onPostComposition(presentTime)) {
@@ -2760,7 +2764,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;
@@ -2897,11 +2901,9 @@
                         const auto* outputLayer =
                             compositionDisplay->getOutputLayerForLayer(layerFe);
                         if (outputLayer) {
-                            // TODO(b/267350616): Rename SdrHdrRatio -> HdrSdrRatio
-                            // everywhere
-                            const float desiredHdrSdrRatio = snapshot.desiredSdrHdrRatio <= 1.f
+                            const float desiredHdrSdrRatio = snapshot.desiredHdrSdrRatio <= 1.f
                                     ? std::numeric_limits<float>::infinity()
-                                    : snapshot.desiredSdrHdrRatio;
+                                    : snapshot.desiredHdrSdrRatio;
                             info.mergeDesiredRatio(desiredHdrSdrRatio);
                             info.numberOfHdrLayers++;
                             const auto displayFrame = outputLayer->getState().displayFrame;
@@ -3713,17 +3715,33 @@
         return;
     }
 
+    std::unordered_set<Layer*> visibleLayers;
+    mDrawingState.traverse([&visibleLayers](Layer* layer) {
+        if (layer->isVisibleForInput()) {
+            visibleLayers.insert(layer);
+        }
+    });
+    bool visibleLayersChanged = false;
+    if (visibleLayers != mVisibleLayers) {
+        visibleLayersChanged = true;
+        mVisibleLayers = std::move(visibleLayers);
+    }
+
     BackgroundExecutor::getInstance().sendCallbacks({[updateWindowInfo,
                                                       windowInfos = std::move(windowInfos),
                                                       displayInfos = std::move(displayInfos),
                                                       inputWindowCommands =
                                                               std::move(mInputWindowCommands),
-                                                      inputFlinger = mInputFlinger, this]() {
+                                                      inputFlinger = mInputFlinger, this,
+                                                      visibleLayersChanged]() {
         ATRACE_NAME("BackgroundExecutor::updateInputFlinger");
         if (updateWindowInfo) {
             mWindowInfosListenerInvoker
-                    ->windowInfosChanged(windowInfos, displayInfos,
-                                         inputWindowCommands.windowInfosReportedListeners);
+                    ->windowInfosChanged(std::move(windowInfos), std::move(displayInfos),
+                                         std::move(
+                                                 inputWindowCommands.windowInfosReportedListeners),
+                                         /* forceImmediateCall= */ visibleLayersChanged ||
+                                                 !inputWindowCommands.focusRequests.empty());
         } else {
             // If there are listeners but no changes to input windows, call the listeners
             // immediately.
@@ -4090,8 +4108,34 @@
         ALOGE("AddClientLayer failed, mNumLayers (%zu) >= MAX_LAYERS (%zu)", mNumLayers.load(),
               MAX_LAYERS);
         static_cast<void>(mScheduler->schedule([=] {
+            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 handleIsAlive: %s", parent->getName().c_str(),
+                              std::to_string(parent->isHandleAlive()).c_str());
+                        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());
+                        }
+                    });
+                }
+            });
+
             ALOGE("Dumping random sampling of on-screen layers: ");
-            mDrawingState.traverse([&](Layer *layer) {
+            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
@@ -4100,6 +4144,8 @@
                     ALOGE("Layer: %s", layer->getName().c_str());
                 }
             });
+            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());
@@ -4145,7 +4191,6 @@
     ATRACE_INT("mTransactionFlags", transactionFlags);
 
     if (const bool scheduled = transactionFlags & mask; !scheduled) {
-        mScheduler->resync();
         scheduleCommit(frameHint);
     } else if (frameHint == FrameHint::kActive) {
         // Even if the next frame is already scheduled, we should reset the idle timer
@@ -4158,20 +4203,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;
     }
 
@@ -4179,7 +4224,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;
@@ -4202,24 +4248,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,
                                                  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;
                 }
@@ -4231,25 +4282,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 &&
@@ -4260,16 +4314,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;
 }
 
@@ -4729,6 +4779,7 @@
         }
         return 0;
     }
+    MUTEX_ALIAS(mStateLock, layer->mFlinger->mStateLock);
 
     ui::LayerStack oldLayerStack = layer->getLayerStack(LayerVector::StateSet::Current);
 
@@ -4929,7 +4980,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;
         }
     }
@@ -5104,6 +5155,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)) {
@@ -5297,6 +5354,7 @@
 
     Mutex::Autolock lock(mStateLock);
     markLayerPendingRemovalLocked(layer);
+    layer->onHandleDestroyed();
     mBufferCountTracker.remove(handle);
     layer.clear();
 
@@ -6848,6 +6906,7 @@
     wp<const DisplayDevice> displayWeak;
     ui::LayerStack layerStack;
     ui::Size reqSize(args.width, args.height);
+    std::unordered_set<uint32_t> excludeLayerIds;
     ui::Dataspace dataspace;
     {
         Mutex::Autolock lock(mStateLock);
@@ -6861,6 +6920,16 @@
             reqSize = display->getLayerStackSpaceRect().getSize();
         }
 
+        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;
+            }
+        }
+
         // 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
@@ -6876,10 +6945,11 @@
     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);
     }
@@ -6922,7 +6992,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);
     }
@@ -7017,13 +7087,34 @@
 
     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);
     });
     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);
@@ -7325,6 +7416,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.
@@ -7343,6 +7435,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);
         });
     }
@@ -7671,6 +7774,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;
@@ -7845,9 +7949,15 @@
                                          ISurfaceComposerClient::eNoColorFill,
                                          gui::LayerMetadata());
             sp<Layer> childMirror;
-            createEffectLayer(mirrorArgs, &unused, &childMirror);
-            childMirror->setClonedChild(layer->createClone());
-            childMirror->reparent(mirrorDisplay.rootHandle);
+            {
+                Mutex::Autolock lock(mStateLock);
+                createEffectLayer(mirrorArgs, &unused, &childMirror);
+                MUTEX_ALIAS(mStateLock, childMirror->mFlinger->mStateLock);
+                childMirror->setClonedChild(layer->createClone());
+                childMirror->reparent(mirrorDisplay.rootHandle);
+            }
+            // lock on mStateLock needs to be released before binder handle gets destroyed
+            unused.clear();
         }
     }
     return true;
@@ -7973,7 +8083,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()) {
@@ -7998,9 +8108,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);
@@ -8013,7 +8162,7 @@
                      .globalShadowSettings = mDrawingState.globalShadowSettings,
                      .supportsBlur = mSupportsBlur,
                      .forceFullDamage = mForceFullDamage,
-                     .parentCrop = {parentCrop},
+                     .parentCrop = parentCrop,
                      .excludeLayerIds = std::move(excludeLayerIds),
                      .supportedLayerGenericMetadata =
                              getHwComposer().getSupportedLayerGenericMetadata(),
@@ -8844,6 +8993,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 42d5db7..04fcfb9 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,
 };
 
@@ -323,6 +329,8 @@
     bool mIgnoreHwcPhysicalDisplayOrientation = false;
 
     void forceFutureUpdate(int delayInMs);
+    const DisplayDevice* getDisplayFromLayerStack(ui::LayerStack)
+            REQUIRES(mStateLock, kMainThreadContext);
 
 protected:
     // We're reference counted, never destroy SurfaceFlinger directly
@@ -492,7 +500,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);
@@ -797,7 +805,7 @@
     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(LayerCreationArgs& args, const sp<IBinder>& handle,
@@ -826,7 +834,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();
 
@@ -845,8 +855,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
@@ -1002,10 +1013,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(
@@ -1381,8 +1392,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;
 
@@ -1408,14 +1422,15 @@
     // 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;
+
+    // Layers visible during the last commit. This set should only be used for testing set equality
+    // and membership. The pointers should not be dereferenced as it's possible the set contains
+    // pointers to freed layers.
+    std::unordered_set<Layer*> mVisibleLayers;
 };
 
 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 2918f7c..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>
@@ -44,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) {
diff --git a/services/surfaceflinger/Tracing/LayerTracing.h b/services/surfaceflinger/Tracing/LayerTracing.h
index 11bb9f4..40b0fbe 100644
--- a/services/surfaceflinger/Tracing/LayerTracing.h
+++ b/services/surfaceflinger/Tracing/LayerTracing.h
@@ -43,9 +43,10 @@
     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();
+    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);
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 8fd6538..57d927b 100644
--- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
+++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
@@ -27,9 +27,7 @@
 namespace android::surfaceflinger {
 
 class FakeExternalTexture : public renderengine::ExternalTexture {
-    const sp<GraphicBuffer> mEmptyBuffer =
-            sp<GraphicBuffer>::make(1u, 1u, PIXEL_FORMAT_RGBA_8888,
-                                    GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_OFTEN);
+    const sp<GraphicBuffer> mEmptyBuffer = nullptr;
     uint32_t mWidth;
     uint32_t mHeight;
     uint64_t mId;
@@ -49,6 +47,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;
 };
 
@@ -587,7 +586,7 @@
     displayInfo.receivesInput = proto.receives_input();
     displayInfo.isSecure = proto.is_secure();
     displayInfo.isPrimary = proto.is_primary();
-    displayInfo.isPrimary = proto.is_virtual();
+    displayInfo.isVirtual = proto.is_virtual();
     displayInfo.rotationFlags = (ui::Transform::RotationFlags)proto.rotation_flags();
     displayInfo.transformHint = (ui::Transform::RotationFlags)proto.transform_hint();
     return displayInfo;
@@ -595,7 +594,7 @@
 
 void TransactionProtoParser::fromProto(
         const google::protobuf::RepeatedPtrField<proto::DisplayInfo>& proto,
-        display::DisplayMap<ui::LayerStack, frontend::DisplayInfo> outDisplayInfos) {
+        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()),
diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.h b/services/surfaceflinger/Tracing/TransactionProtoParser.h
index 50944fc..d6c98e1 100644
--- a/services/surfaceflinger/Tracing/TransactionProtoParser.h
+++ b/services/surfaceflinger/Tracing/TransactionProtoParser.h
@@ -49,15 +49,16 @@
     proto::TransactionState toProto(const std::map<uint32_t /* layerId */, TracingLayerState>&);
     proto::LayerCreationArgs toProto(const LayerCreationArgs& args);
     proto::LayerState toProto(const ResolvedComposerState&);
-    proto::DisplayInfo toProto(const frontend::DisplayInfo&, uint32_t layerStack);
+    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&, LayerCreationArgs& outArgs);
     std::unique_ptr<FlingerDataMapper> mMapper;
-    frontend::DisplayInfo fromProto(const proto::DisplayInfo&);
-    void fromProto(const google::protobuf::RepeatedPtrField<proto::DisplayInfo>&,
-                   display::DisplayMap<ui::LayerStack, frontend::DisplayInfo> outDisplayInfos);
+    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::DisplayState toProto(const DisplayState&);
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.cpp b/services/surfaceflinger/Tracing/TransactionTracing.cpp
index 26ed878..87a633f 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.cpp
+++ b/services/surfaceflinger/Tracing/TransactionTracing.cpp
@@ -271,6 +271,7 @@
         for (const proto::LayerState& layerState : transaction.layer_changes()) {
             auto it = mStartingStates.find(layerState.layer_id());
             if (it == mStartingStates.end()) {
+                // 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;
             }
diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
index 2418cf2..55004c5 100644
--- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
@@ -14,13 +14,6 @@
  * limitations under the License.
  */
 
-#include <memory>
-#include <vector>
-#include "FrontEnd/LayerCreationArgs.h"
-#include "FrontEnd/RequestedLayerState.h"
-#include "Tracing/LayerTracing.h"
-#include "TransactionState.h"
-#include "cutils/properties.h"
 #undef LOG_TAG
 #define LOG_TAG "LayerTraceGenerator"
 //#define LOG_NDEBUG 0
@@ -32,8 +25,15 @@
 #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"
 
@@ -62,8 +62,11 @@
 
     LayerTracing layerTracing;
     layerTracing.setTraceFlags(LayerTracing::TRACE_INPUT | LayerTracing::TRACE_BUFFERS);
-    layerTracing.setBufferSize(512 * 1024 * 1024); // 512MB buffer size
+    // 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++) {
@@ -80,6 +83,7 @@
         for (int j = 0; j < entry.added_layers_size(); j++) {
             LayerCreationArgs args;
             parser.fromProto(entry.added_layers(j), args);
+            ALOGV("       %s", args.getDebugString().c_str());
             addedLayers.emplace_back(std::make_unique<frontend::RequestedLayerState>(args));
         }
 
@@ -88,12 +92,27 @@
         for (int j = 0; j < entry.transactions_size(); j++) {
             // apply transactions
             TransactionState transaction = parser.fromProto(entry.transactions(j));
+            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));
         }
 
+        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));
         }
 
@@ -104,7 +123,7 @@
 
         // apply updates
         lifecycleManager.addLayers(std::move(addedLayers));
-        lifecycleManager.applyTransactions(transactions);
+        lifecycleManager.applyTransactions(transactions, /*ignoreUnknownHandles=*/true);
         lifecycleManager.onHandlesDestroyed(destroyedHandles, /*ignoreUnknownHandles=*/true);
 
         if (lifecycleManager.getGlobalChanges().test(
@@ -141,8 +160,10 @@
         auto displayProtos = LayerProtoHelper::writeDisplayInfoToProto(displayInfos);
         layerTracing.notify(visibleRegionsDirty, entry.elapsed_realtime_nanos(), entry.vsync_id(),
                             &layersProto, {}, &displayProtos);
+        layerTracing.appendToStream(out);
     }
-    layerTracing.disable(outputLayersTracePath);
+    layerTracing.disable("", /*writeToFile=*/false);
+    out.close();
     ALOGD("End of generating trace file. File written to %s", outputLayersTracePath);
     return true;
 }
diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
index 292083b..856fbbb 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
@@ -25,20 +25,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 +43,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 +57,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 +85,81 @@
 }
 
 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) {
+    reportedListeners.insert(sp<WindowInfosListenerInvoker>::fromExisting(this));
+    auto callListeners = [this, windowInfos = std::move(windowInfos),
+                          displayInfos = std::move(displayInfos)](
+                                 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));
+
+        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(windowInfos, displayInfos, 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);
+            mReportedListenersDelayed.merge(reportedListeners);
+            return;
         }
+
+        mWindowInfosChangedDelayed = nullptr;
+        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;
+        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..4da9828 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.h
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.h
@@ -23,34 +23,42 @@
 #include <android/gui/IWindowInfosReportedListener.h>
 #include <binder/IBinder.h>
 #include <ftl/small_map.h>
+#include <gui/SpHash.h>
 #include <utils/Mutex.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);
+
+    binder::Status onWindowInfosReported() override;
 
 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);
+    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..a9247fe 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);
 }
 
@@ -555,8 +555,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_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index 6074bb7..c1bab0e 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))));
     }
 
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
index c088e7b..4304259 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>();
@@ -166,7 +174,7 @@
                               {mFdp.ConsumeIntegral<int32_t>(),
                                mFdp.ConsumeIntegral<int32_t>()} /*reqSize*/,
                               mFdp.PickValueInArray(kDataspaces), mFdp.ConsumeBool(),
-                              mFdp.ConsumeBool());
+                              mFdp.ConsumeBool(), getFuzzedTransform(), getFuzzedRect());
     layerArea.render([]() {} /*drawLayers*/);
 
     if (!ownsHandle) {
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
index e6be9a8..a32750e 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
@@ -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/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/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..f4a8f03 100644
--- a/services/surfaceflinger/tests/WindowInfosListener_test.cpp
+++ b/services/surfaceflinger/tests/WindowInfosListener_test.cpp
@@ -17,6 +17,7 @@
 #include <gtest/gtest.h>
 #include <gui/SurfaceComposerClient.h>
 #include <private/android_filesystem_config.h>
+#include <cstdint>
 #include <future>
 
 namespace android {
@@ -65,14 +66,14 @@
     }
 };
 
-std::optional<WindowInfo> findMatchingWindowInfo(WindowInfo targetWindowInfo,
-                                                 std::vector<WindowInfo> windowInfos) {
-    for (WindowInfo windowInfo : windowInfos) {
+const WindowInfo* findMatchingWindowInfo(const WindowInfo& targetWindowInfo,
+                                         const std::vector<WindowInfo>& windowInfos) {
+    for (const WindowInfo& windowInfo : windowInfos) {
         if (windowInfo.token == targetWindowInfo.token) {
-            return windowInfo;
+            return &windowInfo;
         }
     }
-    return std::nullopt;
+    return nullptr;
 }
 
 TEST_F(WindowInfosListenerTest, WindowInfoAddedAndRemoved) {
@@ -93,14 +94,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 +133,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 +142,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/TransactionTraceTestSuite.cpp b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
index 7355c35..2b29530 100644
--- a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
+++ b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
@@ -22,6 +22,7 @@
 #include <string>
 #include <unordered_map>
 
+#include <LayerProtoHelper.h>
 #include <LayerTraceGenerator.h>
 #include <Tracing/TransactionProtoParser.h>
 #include <layerproto/LayerProtoHeader.h>
@@ -94,11 +95,14 @@
     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) {
@@ -109,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> {
@@ -119,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(),
@@ -127,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) {
@@ -140,32 +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);
-        LayerInfo layerInfo = getLayerInfoFromProto(layer);
-        expectedLayers.push_back(layerInfo);
-    }
-    std::sort(expectedLayers.begin(), expectedLayers.end(), compareById);
-
-    std::unordered_map<int32_t /* snapshotId*/, int32_t /*layerId*/> snapshotIdToLayerId;
-    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);
-        LayerInfo layerInfo = getLayerInfoFromProto(layer);
-        snapshotIdToLayerId[layerInfo.id] = static_cast<int32_t>(layer.original_id());
-        actualLayers.push_back(layerInfo);
-    }
-
-    for (auto& layer : actualLayers) {
-        layer.id = snapshotIdToLayerId[layer.id];
-        auto it = snapshotIdToLayerId.find(layer.parent);
-        layer.parent = it == snapshotIdToLayerId.end() ? -1 : it->second;
-    }
-
-    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/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/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 df3ffd2..201d37f 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,6 @@
         ":libsurfaceflinger_mock_sources",
         ":libsurfaceflinger_sources",
         "libsurfaceflinger_unittest_main.cpp",
-        "AidlPowerHalWrapperTest.cpp",
         "CompositionTest.cpp",
         "DisplayIdGeneratorTest.cpp",
         "DisplayTransactionTest.cpp",
@@ -196,6 +195,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..156007b 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -204,7 +204,7 @@
 
     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;
                 });
@@ -731,7 +731,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;
                 });
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 ddf3363..e4f49e8 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
@@ -466,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);
@@ -662,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);
@@ -678,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 = {};
@@ -693,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 = {};
@@ -710,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 = {};
@@ -729,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 5b3c7ef..4301186 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
@@ -278,6 +278,36 @@
         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;
 };
 
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 14b8e4b..97ef5a2 100644
--- a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
@@ -36,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();
     }
@@ -51,7 +51,7 @@
         mActualLayersDestroyed.clear();
     }
 
-    std::unordered_set<uint32_t> mActualLayersAdded;
+    std::vector<uint32_t> mActualLayersAdded;
     std::unordered_set<uint32_t> mActualLayersDestroyed;
 };
 
@@ -318,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);
@@ -352,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});
 }
@@ -381,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 b8c4781..b8a7446 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -79,6 +79,7 @@
                                         .displays = mFrontEndDisplayInfos,
                                         .displayChanges = hasDisplayChanges,
                                         .globalShadowSettings = globalShadowSettings,
+                                        .supportsBlur = true,
                                         .supportedLayerGenericMetadata = {},
                                         .genericLayerMetadataKeyMap = {}};
         actualBuilder.update(args);
@@ -308,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)
@@ -327,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);
 }
 
@@ -346,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);
@@ -370,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}};
@@ -382,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);
@@ -392,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 63ed87b..d63e187 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -3026,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/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_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/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index fc9e653..cfa366f 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -48,10 +48,12 @@
 #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 {
 namespace renderengine {
@@ -344,10 +346,6 @@
         layer->mDrawingParent = drawingParent;
     }
 
-    void setPowerHintSessionMode(bool early, bool late) {
-        mFlinger->mPowerHintSessionMode = {.late = late, .early = early};
-    }
-
     /* ------------------------------------------------------------------------
      * Forwarding for functions being tested
      */
@@ -379,8 +377,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) {
@@ -446,8 +444,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,
@@ -527,6 +527,24 @@
         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);
+    }
+
     /* ------------------------------------------------------------------------
      * Read-only access to private data to assert post-conditions.
      */
@@ -964,6 +982,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 d4e2357..03c4e71 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -294,7 +294,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 +303,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;
     }
 
@@ -601,6 +607,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 +984,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());
diff --git a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
index 3dea189..dd72174 100644
--- a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
@@ -19,6 +19,7 @@
 #include <limits> // std::numeric_limits
 
 #include <gui/SurfaceComposerClient.h>
+#include <ui/Rotation.h>
 #include "LayerProtoHelper.h"
 
 #include "Tracing/TransactionProtoParser.h"
@@ -103,4 +104,48 @@
     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 74541ac..82aac7e 100644
--- a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
@@ -21,8 +21,10 @@
 #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"
 
@@ -305,4 +307,24 @@
     EXPECT_EQ(proto.entry(0).transactions(0).layer_changes().size(), 2);
     EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(1).z(), 43);
 }
+
+// Verify we can write the layers traces by entry to reduce mem pressure
+// on the system when generating large traces.
+TEST(LayerTraceTest, canStreamLayersTrace) {
+    LayersTraceFileProto inProto = LayerTracing::createTraceFileProto();
+    inProto.add_entry();
+    inProto.add_entry();
+
+    std::string output;
+    inProto.SerializeToString(&output);
+    LayersTraceFileProto inProto2 = LayerTracing::createTraceFileProto();
+    inProto2.add_entry();
+    std::string output2;
+    inProto2.SerializeToString(&output2);
+
+    LayersTraceFileProto outProto;
+    outProto.ParseFromString(output + output2);
+    // magic?
+    EXPECT_EQ(outProto.entry().size(), 3);
+}
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/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/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/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;
 }